Merge "Revert "Remove more calls to addFullExpansionListener"" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 906536d..a1375d7 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -14,6 +14,7 @@
 
 aconfig_srcjars = [
     ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+    ":android.companion.flags-aconfig-java{.generated_srcjars}",
     ":android.content.pm.flags-aconfig-java{.generated_srcjars}",
     ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",
     ":android.nfc.flags-aconfig-java{.generated_srcjars}",
@@ -35,7 +36,9 @@
     ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
     ":sdk_sandbox_flags_lib{.generated_srcjars}",
     ":android.permission.flags-aconfig-java{.generated_srcjars}",
+    ":android.database.sqlite-aconfig-java{.generated_srcjars}",
     ":hwui_flags_java_lib{.generated_srcjars}",
+    ":framework_graphics_flags_java_lib{.generated_srcjars}",
     ":display_flags_lib{.generated_srcjars}",
     ":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
     ":android.multiuser.flags-aconfig-java{.generated_srcjars}",
@@ -44,6 +47,7 @@
     ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
     ":android.service.voice.flags-aconfig-java{.generated_srcjars}",
     ":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
+    ":com.android.net.flags-aconfig-java{.generated_srcjars}",
 ]
 
 filegroup {
@@ -331,6 +335,19 @@
 
 }
 
+// SQLite
+aconfig_declarations {
+    name: "android.database.sqlite-aconfig",
+    package: "android.database.sqlite",
+    srcs: ["core/java/android/database/sqlite/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.database.sqlite-aconfig-java",
+    aconfig_declarations: "android.database.sqlite-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Biometrics
 aconfig_declarations {
     name: "android.hardware.biometrics.flags-aconfig",
@@ -351,6 +368,12 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "framework_graphics_flags_java_lib",
+    aconfig_declarations: "framework_graphics_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Display
 java_aconfig_library {
     name: "display_flags_lib",
@@ -448,7 +471,7 @@
     package: "android.service.autofill",
     srcs: [
         "services/autofill/bugfixes.aconfig",
-        "services/autofill/features.aconfig"
+        "services/autofill/features.aconfig",
     ],
 }
 
@@ -457,3 +480,23 @@
     aconfig_declarations: "android.service.autofill.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// Companion
+aconfig_declarations {
+    name: "android.companion.flags-aconfig",
+    package: "android.companion",
+    srcs: ["core/java/android/companion/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.companion.flags-aconfig-java",
+    aconfig_declarations: "android.companion.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// CoreNetworking
+java_aconfig_library {
+    name: "com.android.net.flags-aconfig-java",
+    aconfig_declarations: "com.android.net.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index bded26a..015487d 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -25,6 +25,6 @@
 
 hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclude.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
 
-ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/packages/SystemUI/ktfmt_includes.txt ${PREUPLOAD_FILES}
+ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/ktfmt_includes.txt ${PREUPLOAD_FILES}
 
 ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES}
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 4e3cb7d..3e835b8 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -2,7 +2,7 @@
 
 flag {
     name: "relax_prefetch_connectivity_constraint_only_on_charger"
-    namespace: "backstagepower"
+    namespace: "backstage_power"
     description: "Only relax a prefetch job's connectivity constraint when the device is charging"
     bug: "299329948"
 }
\ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 3cbee5d0..384a480 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -173,8 +173,6 @@
 
 import dalvik.annotation.optimization.NeverCompile;
 
-import libcore.util.EmptyArray;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -260,7 +258,7 @@
     /**
      * A map from uid to the last op-mode we have seen for
      * {@link AppOpsManager#OP_SCHEDULE_EXACT_ALARM}. Used for evaluating permission state change
-     * when the denylist changes.
+     * when the app-op changes.
      */
     @VisibleForTesting
     @GuardedBy("mLock")
@@ -671,9 +669,6 @@
     @VisibleForTesting
     final class Constants implements DeviceConfig.OnPropertiesChangedListener,
             EconomyManagerInternal.TareStateChangeListener {
-        @VisibleForTesting
-        static final int MAX_EXACT_ALARM_DENY_LIST_SIZE = 250;
-
         // Key names stored in the settings value.
         @VisibleForTesting
         static final String KEY_MIN_FUTURITY = "min_futurity";
@@ -727,8 +722,6 @@
         @VisibleForTesting
         static final String KEY_PRIORITY_ALARM_DELAY = "priority_alarm_delay";
         @VisibleForTesting
-        static final String KEY_EXACT_ALARM_DENY_LIST = "exact_alarm_deny_list";
-        @VisibleForTesting
         static final String KEY_MIN_DEVICE_IDLE_FUZZ = "min_device_idle_fuzz";
         @VisibleForTesting
         static final String KEY_MAX_DEVICE_IDLE_FUZZ = "max_device_idle_fuzz";
@@ -835,13 +828,6 @@
         public long PRIORITY_ALARM_DELAY = DEFAULT_PRIORITY_ALARM_DELAY;
 
         /**
-         * Read-only set of apps that won't get SCHEDULE_EXACT_ALARM when the app-op mode for
-         * OP_SCHEDULE_EXACT_ALARM is MODE_DEFAULT. Since this is read-only and volatile, this can
-         * be accessed without synchronizing on {@link #mLock}.
-         */
-        public volatile Set<String> EXACT_ALARM_DENY_LIST = Collections.emptySet();
-
-        /**
          * Minimum time interval that an IDLE_UNTIL will be pulled earlier to a subsequent
          * WAKE_FROM_IDLE alarm.
          */
@@ -1025,21 +1011,6 @@
                             PRIORITY_ALARM_DELAY = properties.getLong(KEY_PRIORITY_ALARM_DELAY,
                                     DEFAULT_PRIORITY_ALARM_DELAY);
                             break;
-                        case KEY_EXACT_ALARM_DENY_LIST:
-                            final String rawValue = properties.getString(KEY_EXACT_ALARM_DENY_LIST,
-                                    "");
-                            final String[] values = rawValue.isEmpty()
-                                    ? EmptyArray.STRING
-                                    : rawValue.split(",", MAX_EXACT_ALARM_DENY_LIST_SIZE + 1);
-                            if (values.length > MAX_EXACT_ALARM_DENY_LIST_SIZE) {
-                                Slog.w(TAG, "Deny list too long, truncating to "
-                                        + MAX_EXACT_ALARM_DENY_LIST_SIZE + " elements.");
-                                updateExactAlarmDenyList(
-                                        Arrays.copyOf(values, MAX_EXACT_ALARM_DENY_LIST_SIZE));
-                            } else {
-                                updateExactAlarmDenyList(values);
-                            }
-                            break;
                         case KEY_MIN_DEVICE_IDLE_FUZZ:
                         case KEY_MAX_DEVICE_IDLE_FUZZ:
                             if (!deviceIdleFuzzBoundariesUpdated) {
@@ -1110,28 +1081,6 @@
             }
         }
 
-        private void updateExactAlarmDenyList(String[] newDenyList) {
-            final Set<String> newSet = Collections.unmodifiableSet(new ArraySet<>(newDenyList));
-            final Set<String> removed = new ArraySet<>(EXACT_ALARM_DENY_LIST);
-            final Set<String> added = new ArraySet<>(newDenyList);
-
-            added.removeAll(EXACT_ALARM_DENY_LIST);
-            removed.removeAll(newSet);
-            if (added.size() > 0) {
-                mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED, added)
-                        .sendToTarget();
-            }
-            if (removed.size() > 0) {
-                mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED, removed)
-                        .sendToTarget();
-            }
-            if (newDenyList.length == 0) {
-                EXACT_ALARM_DENY_LIST = Collections.emptySet();
-            } else {
-                EXACT_ALARM_DENY_LIST = newSet;
-            }
-        }
-
         private void updateDeviceIdleFuzzBoundaries() {
             final DeviceConfig.Properties properties = DeviceConfig.getProperties(
                     DeviceConfig.NAMESPACE_ALARM_MANAGER,
@@ -1277,9 +1226,6 @@
             TimeUtils.formatDuration(PRIORITY_ALARM_DELAY, pw);
             pw.println();
 
-            pw.print(KEY_EXACT_ALARM_DENY_LIST, EXACT_ALARM_DENY_LIST);
-            pw.println();
-
             pw.print(KEY_MIN_DEVICE_IDLE_FUZZ);
             pw.print("=");
             TimeUtils.formatDuration(MIN_DEVICE_IDLE_FUZZ, pw);
@@ -2114,14 +2060,10 @@
                                             ? permissionState
                                             : (newMode == AppOpsManager.MODE_ALLOWED);
                                 } else {
-                                    final boolean allowedByDefault =
-                                            !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName);
                                     hadPermission = (oldMode == AppOpsManager.MODE_DEFAULT)
-                                            ? allowedByDefault
-                                            : (oldMode == AppOpsManager.MODE_ALLOWED);
+                                            || (oldMode == AppOpsManager.MODE_ALLOWED);
                                     hasPermission = (newMode == AppOpsManager.MODE_DEFAULT)
-                                            ? allowedByDefault
-                                            : (newMode == AppOpsManager.MODE_ALLOWED);
+                                            || (newMode == AppOpsManager.MODE_ALLOWED);
                                 }
 
                                 if (hadPermission && !hasPermission) {
@@ -2769,11 +2711,8 @@
             // Compatibility permission check for older apps.
             final int mode = mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid,
                     packageName);
-            if (mode == AppOpsManager.MODE_DEFAULT) {
-                hasPermission = !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName);
-            } else {
-                hasPermission = (mode == AppOpsManager.MODE_ALLOWED);
-            }
+            hasPermission = (mode == AppOpsManager.MODE_DEFAULT)
+                    || (mode == AppOpsManager.MODE_ALLOWED);
         }
         mStatLogger.logDurationStat(Stats.HAS_SCHEDULE_EXACT_ALARM, start);
         return hasPermission;
@@ -3993,63 +3932,6 @@
     }
 
     /**
-     * Called when the {@link Constants#EXACT_ALARM_DENY_LIST}, changes with the packages that
-     * either got added or deleted.
-     * These packages may lose or gain the SCHEDULE_EXACT_ALARM permission.
-     *
-     * Note that these packages don't need to be installed on the device, but if they are and they
-     * do undergo a permission change, we will handle them appropriately.
-     *
-     * This should not be called with the lock held as it calls out to other services.
-     * This is not expected to get called frequently.
-     */
-    void handleChangesToExactAlarmDenyList(ArraySet<String> changedPackages, boolean added) {
-        Slog.w(TAG, "Packages " + changedPackages + (added ? " added to" : " removed from")
-                + " the exact alarm deny list.");
-
-        final int[] startedUserIds = mActivityManagerInternal.getStartedUserIds();
-
-        for (int i = 0; i < changedPackages.size(); i++) {
-            final String changedPackage = changedPackages.valueAt(i);
-            for (final int userId : startedUserIds) {
-                final int uid = mPackageManagerInternal.getPackageUid(changedPackage, 0, userId);
-                if (uid <= 0) {
-                    continue;
-                }
-                if (!isExactAlarmChangeEnabled(changedPackage, userId)) {
-                    continue;
-                }
-                if (isScheduleExactAlarmDeniedByDefault(changedPackage, userId)) {
-                    continue;
-                }
-                if (hasUseExactAlarmInternal(changedPackage, uid)) {
-                    continue;
-                }
-                if (!mExactAlarmCandidates.contains(UserHandle.getAppId(uid))) {
-                    // Permission isn't requested, deny list doesn't matter.
-                    continue;
-                }
-                final int appOpMode;
-                synchronized (mLock) {
-                    appOpMode = mLastOpScheduleExactAlarm.get(uid,
-                            AppOpsManager.opToDefaultMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM));
-                }
-                if (appOpMode != AppOpsManager.MODE_DEFAULT) {
-                    // Deny list doesn't matter.
-                    continue;
-                }
-                // added: true => package was added to the deny list
-                // added: false => package was removed from the deny list
-                if (added) {
-                    removeExactAlarmsOnPermissionRevoked(uid, changedPackage, /*killUid = */ true);
-                } else {
-                    sendScheduleExactAlarmPermissionStateChangedBroadcast(changedPackage, userId);
-                }
-            }
-        }
-    }
-
-    /**
      * Called when an app loses the permission to use exact alarms. This will happen when the app
      * no longer has either {@link Manifest.permission#SCHEDULE_EXACT_ALARM} or
      * {@link Manifest.permission#USE_EXACT_ALARM}.
@@ -4931,8 +4813,8 @@
         public static final int CHARGING_STATUS_CHANGED = 6;
         public static final int REMOVE_FOR_CANCELED = 7;
         public static final int REMOVE_EXACT_ALARMS = 8;
-        public static final int EXACT_ALARM_DENY_LIST_PACKAGES_ADDED = 9;
-        public static final int EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED = 10;
+        // Unused id 9
+        // Unused id 10
         public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11;
         public static final int TARE_AFFORDABILITY_CHANGED = 12;
         public static final int CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE = 13;
@@ -5041,12 +4923,6 @@
                     String packageName = (String) msg.obj;
                     removeExactAlarmsOnPermissionRevoked(uid, packageName, /*killUid = */true);
                     break;
-                case EXACT_ALARM_DENY_LIST_PACKAGES_ADDED:
-                    handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, true);
-                    break;
-                case EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED:
-                    handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, false);
-                    break;
                 case REFRESH_EXACT_ALARM_CANDIDATES:
                     refreshExactAlarmCandidates();
                     break;
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 2d9c988..fa4bc0f 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -438,6 +438,26 @@
     full_api_surface_stub: "android_module_lib_stubs_current_full.from-text",
 }
 
+// This module generates a stub jar that is a union of the test and module lib
+// non-updatable api contributions. Modules should not depend on the stub jar
+// generated from this module, as this module is strictly used for hiddenapi only.
+java_api_library {
+    name: "android-non-updatable.stubs.test_module_lib",
+    api_surface: "module_lib",
+    api_contributions: [
+        "api-stubs-docs-non-updatable.api.contribution",
+        "system-api-stubs-docs-non-updatable.api.contribution",
+        "test-api-stubs-docs-non-updatable.api.contribution",
+        "module-lib-api-stubs-docs-non-updatable.api.contribution",
+    ],
+    defaults: ["android-non-updatable_from_text_defaults"],
+    full_api_surface_stub: "android_test_module_lib_stubs_current.from-text",
+
+    // This module is only used for hiddenapi, and other modules should not
+    // depend on this module.
+    visibility: ["//visibility:private"],
+}
+
 java_defaults {
     name: "android_stubs_dists_default",
     dist: {
@@ -757,6 +777,30 @@
 }
 
 java_api_library {
+    name: "android_test_module_lib_stubs_current.from-text",
+    api_surface: "module-lib",
+    defaults: [
+        "android_stubs_current_contributions",
+        "android_system_stubs_current_contributions",
+        "android_test_stubs_current_contributions",
+        "android_module_lib_stubs_current_contributions",
+    ],
+    libs: [
+        "android_module_lib_stubs_current_full.from-text",
+        "stub-annotations",
+    ],
+    api_contributions: [
+        "test-api-stubs-docs-non-updatable.api.contribution",
+    ],
+
+    // This module is only used to build android-non-updatable.stubs.test_module_lib
+    // and other modules should not depend on this module.
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+java_api_library {
     name: "android_system_server_stubs_current.from-text",
     api_surface: "system-server",
     api_contributions: [
diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline
index 1f023bd..c28480e 100644
--- a/api/javadoc-lint-baseline
+++ b/api/javadoc-lint-baseline
@@ -86,28 +86,12 @@
 android/app/job/JobParameters.java:128: lint: Unresolved link/see tag "android.app.job.JobInfo.Builder#setPeriodic(boolean) periodic jobs" in android.app.job.JobParameters [101]
 android/app/sdksandbox/AppOwnedSdkSandboxInterface.java:9: lint: Unresolved link/see tag "SdkSandboxController#getAppOwnedSdkSandboxInterfaces" in android.app.sdksandbox.AppOwnedSdkSandboxInterface [101]
 android/app/sdksandbox/SdkSandboxManager.java:112: lint: Unresolved link/see tag "AppOwnedSdkSandboxInterfaces" in android.app.sdksandbox.SdkSandboxManager [101]
-android/companion/CompanionDeviceService.java:273: lint: Unresolved link/see tag "android.companion.AssociationInfo#isSelfManaged() self-managed" in android.companion.CompanionDeviceService [101]
-android/companion/CompanionDeviceService.java:282: lint: Unresolved link/see tag "android.companion.AssociationInfo#isSelfManaged() self-managed" in android.companion.CompanionDeviceService [101]
-android/companion/virtual/VirtualDevice.java:15: lint: Unresolved link/see tag "android.companion.virtual.VirtualDeviceManager.VirtualDevice VirtualDeviceManager.VirtualDevice" in android.companion.virtual.VirtualDevice [101]
-android/companion/virtual/VirtualDevice.java:70: lint: Unresolved link/see tag "android.companion.virtual.VirtualDeviceParams.Builder#setName(String)" in android.companion.virtual.VirtualDevice [101]
 android/content/AttributionSource.java:291: lint: Unresolved link/see tag "setNextAttributionSource" in android.content.AttributionSource.Builder [101]
 android/content/Context.java:2872: lint: Unresolved link/see tag "android.telephony.MmsManager" in android.content.Context [101]
 android/content/Intent.java:4734: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
 android/content/Intent.java:4760: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
 android/content/Intent.java:4778: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
 android/content/Intent.java:4802: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
-android/credentials/CreateCredentialException.java:22: lint: Unresolved link/see tag "android.credentials.CredentialManager#createCredential(android.credentials.CreateCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#createCredential(CreateCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.CreateCredentialException [101]
-android/credentials/CreateCredentialException.java:101: lint: Unresolved link/see tag "android.credentials.CredentialManager#createCredential(android.credentials.CreateCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#createCredential(CreateCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.CreateCredentialException [101]
-android/credentials/CreateCredentialRequest.java:107: lint: Unresolved link/see tag "androidx.credentials.CreateCredentialRequest" in android.credentials.CreateCredentialRequest.Builder [101]
-android/credentials/CredentialDescription.java:89: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mSupportedElementKeys CredentialDescription#mSupportedElementKeys" in android.credentials.CredentialDescription [101]
-android/credentials/CredentialDescription.java:89: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mType CredentialDescription#mType" in android.credentials.CredentialDescription [101]
-android/credentials/CredentialDescription.java:101: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mSupportedElementKeys CredentialDescription#mSupportedElementKeys" in android.credentials.CredentialDescription [101]
-android/credentials/CredentialDescription.java:101: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mType CredentialDescription#mType" in android.credentials.CredentialDescription [101]
-android/credentials/GetCredentialException.java:22: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.GetCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.GetCredentialException [101]
-android/credentials/GetCredentialException.java:103: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.GetCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver)" in android.credentials.GetCredentialException [101]
-android/credentials/PrepareGetCredentialResponse.java:20: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse [101]
-android/credentials/PrepareGetCredentialResponse.java:68: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse [101]
-android/credentials/PrepareGetCredentialResponse.java:83: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle [101]
 android/graphics/Paint.java:838: lint: Unresolved link/see tag "android.annotation.ColorLong ColorLong" in android.graphics.Paint [101]
 android/graphics/text/LineBreaker.java:246: lint: Unresolved link/see tag "StaticLayout.Builder#setUseBoundsForWidth(boolean)" in android.graphics.text.LineBreaker.Builder [101]
 android/hardware/camera2/CameraCharacteristics.java:2169: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101]
@@ -158,8 +142,6 @@
 android/media/AudioManager.java:311: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]
 android/media/AudioManager.java:313: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]
 android/media/AudioMetadata.java:118: lint: Unresolved link/see tag "android.media.AudioPresentation.ContentClassifier One of {@link android.media.AudioPresentation#CONTENT_UNKNOWN AudioPresentation#CONTENT_UNKNOWN},     {@link android.media.AudioPresentation#CONTENT_MAIN AudioPresentation#CONTENT_MAIN},     {@link android.media.AudioPresentation#CONTENT_MUSIC_AND_EFFECTS AudioPresentation#CONTENT_MUSIC_AND_EFFECTS},     {@link android.media.AudioPresentation#CONTENT_VISUALLY_IMPAIRED AudioPresentation#CONTENT_VISUALLY_IMPAIRED},     {@link android.media.AudioPresentation#CONTENT_HEARING_IMPAIRED AudioPresentation#CONTENT_HEARING_IMPAIRED},     {@link android.media.AudioPresentation#CONTENT_DIALOG AudioPresentation#CONTENT_DIALOG},     {@link android.media.AudioPresentation#CONTENT_COMMENTARY AudioPresentation#CONTENT_COMMENTARY},     {@link android.media.AudioPresentation#CONTENT_EMERGENCY AudioPresentation#CONTENT_EMERGENCY},     {@link android.media.AudioPresentation#CONTENT_VOICEOVER AudioPresentation#CONTENT_VOICEOVER}." in android.media.AudioMetadata.Format [101]
-android/media/MediaRouter2.java:162: lint: Unresolved link/see tag "#getInstance(android.content.Context,java.lang.String)" in android.media.MediaRouter2 [101]
-android/media/midi/MidiUmpDeviceService.java:-1: lint: Unresolved link/see tag "#MidiDeviceService" in android.media.midi.MidiUmpDeviceService [101]
 android/media/tv/SectionRequest.java:44: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.SectionRequest [101]
 android/media/tv/SectionResponse.java:39: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.SectionResponse [101]
 android/media/tv/TableRequest.java:48: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.TableRequest [101]
@@ -205,17 +187,6 @@
 android/security/KeyStoreException.java:27: lint: Unresolved link/see tag "android.security.KeyStoreException.PublicErrorCode PublicErrorCode" in android.security.KeyStoreException [101]
 android/service/autofill/FillResponse.java:86: lint: Unresolved link/see tag "setFieldClassificationIds" in android.service.autofill.FillResponse.Builder [101]
 android/service/autofill/SaveInfo.java:623: lint: Unresolved link/see tag "FillRequest.getHints()" in android.service.autofill.SaveInfo.Builder [101]
-android/service/credentials/Action.java:3: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.Action [101]
-android/service/credentials/Action.java:3: lint: Unresolved link/see tag "androidx.credentials.provider.Action" in android.service.credentials.Action [101]
-android/service/credentials/BeginCreateCredentialResponse.java:85: lint: Unresolved link/see tag "Manifest.permission.PROVIDE_REMOTE_CREDENTIALS" in android.service.credentials.BeginCreateCredentialResponse.Builder [101]
-android/service/credentials/BeginGetCredentialResponse.java:80: lint: Unresolved link/see tag "Manifest.permission.PROVIDE_REMOTE_CREDENTIALS" in android.service.credentials.BeginGetCredentialResponse.Builder [101]
-android/service/credentials/CallingAppInfo.java:73: lint: Unresolved link/see tag "android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN" in android.service.credentials.CallingAppInfo [101]
-android/service/credentials/CreateEntry.java:6: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.CreateEntry [101]
-android/service/credentials/CreateEntry.java:6: lint: Unresolved link/see tag "androidx.credentials.provider.CreateEntry" in android.service.credentials.CreateEntry [101]
-android/service/credentials/CredentialEntry.java:11: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.CredentialEntry [101]
-android/service/credentials/CredentialEntry.java:11: lint: Unresolved link/see tag "androidx.credentials.provider.CredentialEntry" in android.service.credentials.CredentialEntry [101]
-android/service/credentials/RemoteEntry.java:13: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.RemoteEntry [101]
-android/service/credentials/RemoteEntry.java:13: lint: Unresolved link/see tag "androidx.credentials.provider.RemoteEntry" in android.service.credentials.RemoteEntry [101]
 android/service/notification/NotificationListenerService.java:417: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService notification assistant" in android.service.notification.NotificationListenerService [101]
 android/service/notification/NotificationListenerService.java:435: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService notification assistant" in android.service.notification.NotificationListenerService [101]
 android/service/notification/NotificationListenerService.java:1155: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService NotificationAssistantService" in android.service.notification.NotificationListenerService.Ranking [101]
@@ -224,33 +195,7 @@
 android/service/voice/VoiceInteractionSession.java:293: lint: Unresolved link/see tag "android.service.voice.VoiceInteractionService#KEY_SHOW_SESSION_ID VoiceInteractionService#KEY_SHOW_SESSION_ID" in android.service.voice.VoiceInteractionSession [101]
 android/text/DynamicLayout.java:141: lint: Unresolved link/see tag "LineBreakconfig" in android.text.DynamicLayout [101]
 android/text/WordSegmentFinder.java:13: lint: Unresolved link/see tag "android.text.method.WordIterator WordIterator" in android.text.WordSegmentFinder [101]
-android/view/InputDevice.java:71: lint: Unresolved link/see tag "InputManagerGlobal.InputDeviceListener" in android.view.InputDevice [101]
 android/view/PixelCopy.java:468: lint: Unresolved link/see tag "android.view.PixelCopy.CopyResultStatus CopyResultStatus" in android.view.PixelCopy.Result [101]
-android/view/ScrollFeedbackProvider.java:-25: lint: Unresolved link/see tag "InputManager" in android.view.ScrollFeedbackProvider [101]
-android/view/ScrollFeedbackProvider.java:-25: lint: Unresolved link/see tag "InputManager#getInputDeviceIds()" in android.view.ScrollFeedbackProvider [101]
-android/view/SurfaceControl.java:823: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101]
-android/view/SurfaceControl.java:900: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101]
-android/view/SurfaceControl.java:908: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101]
-android/view/View.java:1647: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setAccessibilityPaneTitle(View, CharSequence)" in android.view.View [101]
-android/view/View.java:4669: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setScreenReaderFocusable(View, boolean)" in android.view.View [101]
-android/view/View.java:4712: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setAccessibilityHeading(View, boolean)" in android.view.View [101]
-android/view/WindowManager.java:230: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowManager [101]
-android/view/WindowManager.java:247: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowManager [101]
-android/view/WindowManager.java:822: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106]
-android/view/WindowManager.java:832: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106]
-android/view/WindowMetrics.java:22: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101]
-android/view/WindowMetrics.java:57: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101]
-android/view/WindowMetrics.java:114: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101]
-android/view/WindowMetrics.java:127: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101]
-android/view/accessibility/AccessibilityNodeInfo.java:368: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#addAccessibilityAction(View, AccessibilityNodeInfoCompat.AccessibilityActionCompat)" in android.view.accessibility.AccessibilityNodeInfo [101]
-android/view/accessibility/AccessibilityNodeInfo.java:3246: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#addAccessibilityAction(View, AccessibilityNodeInfoCompat.AccessibilityActionCompat)" in android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction [101]
-android/view/displayhash/DisplayHashResultCallback.java:38: lint: Unresolved link/see tag "android.view.displayhash.DisplayHashResultCallback.DisplayHashErrorCode DisplayHashErrorCode" in android.view.displayhash.DisplayHashResultCallback [101]
-android/view/inputmethod/EditorInfo.java:107: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.EditorInfo [101]
-android/view/inputmethod/EditorInfo.java:122: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.EditorInfo [101]
-android/view/inputmethod/InputMethodManager.java:423: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101]
-android/view/inputmethod/InputMethodManager.java:447: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101]
-android/view/inputmethod/InputMethodManager.java:456: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101]
-android/view/inspector/PropertyReader.java:141: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.view.inspector.PropertyReader [101]
 android/window/BackEvent.java:24: lint: Unresolved link/see tag "android.window.BackMotionEvent BackMotionEvent" in android.window.BackEvent [101]
 
 android/net/wifi/SoftApConfiguration.java:173: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setShutdownTimeoutMillis(long)" in android.net.wifi.SoftApConfiguration [101]
@@ -261,21 +206,11 @@
 android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "#STATE_ERROR" in android [101]
 android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "Callback#onFailure" in android [101]
 android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "Callback#onUnknownFailure" in android [101]
-android/view/animation/AnimationUtils.java:64: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in android.view.animation.AnimationUtils [101]
-android/view/contentcapture/ContentCaptureSession.java:188: lint: Unresolved link/see tag "UPSIDE_DOWN_CAKE" in android.view.contentcapture.ContentCaptureSession [101]
 com/android/internal/policy/PhoneWindow.java:172: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in com.android.internal.policy.PhoneWindow [101]
 
-com/android/server/companion/virtual/VirtualDeviceImpl.java:134: lint: Unresolved link/see tag "DisplayManager" in android [101]
-com/android/server/companion/virtual/VirtualDeviceImpl.java:134: lint: Unresolved link/see tag "VirtualDeviceManager.VirtualDevice" in android [101]
 com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "IdentifierType#DAB_SID_EXT" in android [101]
 com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT" in android [101]
 com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "RadioTuner" in android [101]
-com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "ComponentName" in android [101]
-com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "IllegalArgumentException" in android [101]
-com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "MediaSession#setMediaButtonBroadcastReceiver(ComponentName)" in android [101]
-com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "IllegalArgumentException" in android [101]
-com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "MediaSession#setMediaButtonReceiver(PendingIntent)" in android [101]
-com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "PendingIntent" in android [101]
 com/android/server/pm/PackageInstallerSession.java:313: lint: Unresolved link/see tag "Build.VERSION_CODES#S API 31" in android [101]
 com/android/server/pm/PackageInstallerSession.java:313: lint: Unresolved link/see tag "PackageInstaller.SessionParams#setRequireUserAction" in android [101]
 com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101]
@@ -284,12 +219,7 @@
 com/android/server/pm/PackageInstallerSession.java:358: lint: Unresolved link/see tag "IntentSender" in android [101]
 com/android/server/devicepolicy/DevicePolicyManagerService.java:860: lint: Unresolved link/see tag "android.security.IKeyChainService#setGrant" in android [101]
 
-android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:-4: lint: Invalid tag: @Override [131]
-android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:-1: lint: Invalid tag: @Override [131]
-android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:2: lint: Invalid tag: @Override [131]
 android/os/BatteryStatsManager.java:260: lint: Invalid tag: @Deprecated [131]
 android/os/BatteryStatsManager.java:275: lint: Invalid tag: @Deprecated [131]
-android/view/WindowManager.java:906: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106]
-android/view/WindowManager.java:916: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106]
 
 java/lang/ClassLoader.java:853: lint: Unknown tag: @systemProperty [103]
diff --git a/core/api/current.txt b/core/api/current.txt
index f908d95..6c0fccc 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -14352,14 +14352,14 @@
   public final class SQLiteDatabase extends android.database.sqlite.SQLiteClosable {
     method public void beginTransaction();
     method public void beginTransactionNonExclusive();
-    method public void beginTransactionReadOnly();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionReadOnly();
     method public void beginTransactionWithListener(@Nullable android.database.sqlite.SQLiteTransactionListener);
     method public void beginTransactionWithListenerNonExclusive(@Nullable android.database.sqlite.SQLiteTransactionListener);
-    method public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
     method public android.database.sqlite.SQLiteStatement compileStatement(String) throws android.database.SQLException;
     method @NonNull public static android.database.sqlite.SQLiteDatabase create(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);
     method @NonNull public static android.database.sqlite.SQLiteDatabase createInMemory(@NonNull android.database.sqlite.SQLiteDatabase.OpenParams);
-    method @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
     method public int delete(@NonNull String, @Nullable String, @Nullable String[]);
     method public static boolean deleteDatabase(@NonNull java.io.File);
     method public void disableWriteAheadLogging();
@@ -14370,13 +14370,13 @@
     method public void execSQL(@NonNull String, @NonNull Object[]) throws android.database.SQLException;
     method public static String findEditTable(String);
     method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs();
-    method public long getLastChangedRowCount();
-    method public long getLastInsertRowId();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastChangedRowCount();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastInsertRowId();
     method public long getMaximumSize();
     method public long getPageSize();
     method public String getPath();
     method @Deprecated public java.util.Map<java.lang.String,java.lang.String> getSyncedTables();
-    method public long getTotalChangedRowCount();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getTotalChangedRowCount();
     method public int getVersion();
     method public boolean inTransaction();
     method public long insert(@NonNull String, @Nullable String, @Nullable android.content.ContentValues);
@@ -14598,7 +14598,7 @@
     method public int update(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues, @Nullable String, @Nullable String[]);
   }
 
-  public final class SQLiteRawStatement implements java.io.Closeable {
+  @FlaggedApi("android.database.sqlite.sqlite_apis_15") public final class SQLiteRawStatement implements java.io.Closeable {
     method public void bindBlob(int, @NonNull byte[]) throws android.database.sqlite.SQLiteException;
     method public void bindBlob(int, @NonNull byte[], int, int) throws android.database.sqlite.SQLiteException;
     method public void bindDouble(int, double) throws android.database.sqlite.SQLiteException;
@@ -15671,7 +15671,7 @@
 
   public final class Gainmap implements android.os.Parcelable {
     ctor public Gainmap(@NonNull android.graphics.Bitmap);
-    ctor public Gainmap(@NonNull android.graphics.Gainmap, @NonNull android.graphics.Bitmap);
+    ctor @FlaggedApi("com.android.graphics.hwui.flags.gainmap_constructor_with_metadata") public Gainmap(@NonNull android.graphics.Gainmap, @NonNull android.graphics.Bitmap);
     method public int describeContents();
     method @NonNull public float getDisplayRatioForFullHdr();
     method @NonNull public float[] getEpsilonHdr();
@@ -16309,7 +16309,7 @@
     method public void arcTo(float, float, float, float, float, float, boolean);
     method public void close();
     method @Deprecated public void computeBounds(@NonNull android.graphics.RectF, boolean);
-    method public void computeBounds(@NonNull android.graphics.RectF);
+    method @FlaggedApi("com.android.graphics.flags.exact_compute_bounds") public void computeBounds(@NonNull android.graphics.RectF);
     method public void conicTo(float, float, float, float, float);
     method public void cubicTo(float, float, float, float, float, float);
     method @NonNull public android.graphics.Path.FillType getFillType();
@@ -33094,6 +33094,22 @@
     method public void onStateChanged(boolean);
   }
 
+  @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public final class PowerMonitor implements android.os.Parcelable {
+    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public int describeContents();
+    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") @NonNull public String getName();
+    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public int getType();
+    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") @NonNull public static final android.os.Parcelable.Creator<android.os.PowerMonitor> CREATOR;
+    field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int POWER_MONITOR_TYPE_CONSUMER = 0; // 0x0
+    field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int POWER_MONITOR_TYPE_MEASUREMENT = 1; // 0x1
+  }
+
+  @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public final class PowerMonitorReadings {
+    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public long getConsumedEnergy(@NonNull android.os.PowerMonitor);
+    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public long getTimestamp(@NonNull android.os.PowerMonitor);
+    field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int ENERGY_UNAVAILABLE = -1; // 0xffffffff
+  }
+
   public class Process {
     ctor public Process();
     method public static final long getElapsedCpuTime();
@@ -33656,6 +33672,8 @@
   }
 
   public class SystemHealthManager {
+    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getPowerMonitorReadings(@NonNull java.util.List<android.os.PowerMonitor>, @Nullable android.os.Handler, @NonNull java.util.function.Consumer<android.os.PowerMonitorReadings>, @NonNull java.util.function.Consumer<java.lang.RuntimeException>);
+    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getSupportedPowerMonitors(@Nullable android.os.Handler, @NonNull java.util.function.Consumer<java.util.List<android.os.PowerMonitor>>);
     method public android.os.health.HealthStats takeMyUidSnapshot();
     method public android.os.health.HealthStats takeUidSnapshot(int);
     method public android.os.health.HealthStats[] takeUidSnapshots(int[]);
@@ -53129,6 +53147,7 @@
     method @Nullable public abstract android.view.View getCurrentFocus();
     method @NonNull public abstract android.view.View getDecorView();
     method public static int getDefaultFeatures(android.content.Context);
+    method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public float getDesiredHdrHeadroom();
     method public android.transition.Transition getEnterTransition();
     method public android.transition.Transition getExitTransition();
     method protected final int getFeatures();
@@ -53198,6 +53217,7 @@
     method public abstract void setDecorCaptionShade(int);
     method public void setDecorFitsSystemWindows(boolean);
     method protected void setDefaultWindowFormat(int);
+    method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0) float);
     method public void setDimAmount(float);
     method public void setElevation(float);
     method public void setEnterTransition(android.transition.Transition);
@@ -53547,6 +53567,7 @@
     method public int describeContents();
     method public int getBlurBehindRadius();
     method public int getColorMode();
+    method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public float getDesiredHdrHeadroom();
     method public int getFitInsetsSides();
     method public int getFitInsetsTypes();
     method public final CharSequence getTitle();
@@ -53556,6 +53577,7 @@
     method public void setBlurBehindRadius(@IntRange(from=0) int);
     method public void setCanPlayMoveAnimation(boolean);
     method public void setColorMode(int);
+    method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0f) float);
     method public void setFitInsetsIgnoringVisibility(boolean);
     method public void setFitInsetsSides(int);
     method public void setFitInsetsTypes(int);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 81579a2..500a12c 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -8,9 +8,7 @@
     field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS";
     field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT";
     field public static final String MAKE_UID_VISIBLE = "android.permission.MAKE_UID_VISIBLE";
-    field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH";
     field public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS";
-    field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH";
   }
 
 }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7dcc7b2..a1da9e0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -299,6 +299,7 @@
     field public static final String RECEIVE_DATA_ACTIVITY_CHANGE = "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE";
     field public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY";
     field public static final String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST";
+    field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA = "android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA";
     field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOX_TRIGGER_AUDIO = "android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO";
     field public static final String RECEIVE_WIFI_CREDENTIAL_CHANGE = "android.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE";
     field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 3716546..eeddeb2 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -30,7 +30,6 @@
     field public static final String MANAGE_APP_OPS_MODES = "android.permission.MANAGE_APP_OPS_MODES";
     field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES";
     field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS";
-    field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH";
     field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
     field public static final String MANAGE_TOAST_RATE_LIMITING = "android.permission.MANAGE_TOAST_RATE_LIMITING";
     field public static final String MODIFY_HDR_CONVERSION_MODE = "android.permission.MODIFY_HDR_CONVERSION_MODE";
@@ -58,7 +57,6 @@
     field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD";
     field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
     field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
-    field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH";
     field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG";
     field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
     field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
@@ -842,7 +840,7 @@
 
 package android.companion {
 
-  public static final class AssociationInfo.Builder {
+  @FlaggedApi("android.companion.new_association_builder") public static final class AssociationInfo.Builder {
     ctor public AssociationInfo.Builder(int, int, @NonNull String);
     ctor public AssociationInfo.Builder(@NonNull android.companion.AssociationInfo);
     method @NonNull public android.companion.AssociationInfo build();
@@ -1183,6 +1181,7 @@
     method @Nullable public CharSequence getLabel(@NonNull android.content.Context);
     method @Nullable public android.graphics.drawable.Drawable getServiceIcon(@NonNull android.content.Context);
     method @NonNull public android.content.pm.ServiceInfo getServiceInfo();
+    method @FlaggedApi("android.credentials.flags.settings_activity_enabled") @Nullable public CharSequence getSettingsActivity();
     method @Nullable public CharSequence getSettingsSubtitle();
     method @NonNull public boolean hasCapability(@NonNull String);
     method public boolean isEnabled();
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 17637df..ecbc9b1 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1487,13 +1487,13 @@
             AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
 
     /**
-     * Allows the assistant app to get the training data from the trusted process to improve the
-     * hotword training model.
+     * Allows the privileged assistant app to receive the training data from the sandboxed hotword
+     * detection service.
      *
      * @hide
      */
-    public static final int OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA =
-            AppProtoEnums.APP_OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA;
+    public static final int OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA =
+            AppProtoEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;
 
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1641,7 +1641,7 @@
             OPSTR_CAMERA_SANDBOXED,
             OPSTR_RECORD_AUDIO_SANDBOXED,
             OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
-            OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA
+            OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA
     })
     public @interface AppOpString {}
 
@@ -2262,13 +2262,13 @@
             "android:receive_sandbox_trigger_audio";
 
     /**
-     * Allows the assistant app to get the training data from the trusted process to improve
-     * the hotword training model.
+     * Allows the privileged assistant app to receive training data from the sandboxed hotword
+     * detection service.
      *
      * @hide
      */
-    public static final String OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA =
-            "android:receive_trusted_process_training_data";
+    public static final String OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA =
+            "android:RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA";
 
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
@@ -2381,7 +2381,8 @@
             OP_FOREGROUND_SERVICE_SPECIAL_USE,
             OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
             OP_USE_FULL_SCREEN_INTENT,
-            OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
+            OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
+            OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA
     };
 
     static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2814,9 +2815,11 @@
                 "RECEIVE_SANDBOX_TRIGGER_AUDIO")
                 .setPermission(Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO)
                 .setDefaultMode(AppOpsManager.MODE_DEFAULT).build(),
-        new AppOpInfo.Builder(OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA,
-                OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA,
-                "RECEIVE_TRUSTED_PROCESS_TRAINING_DATA").build()
+        new AppOpInfo.Builder(OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
+                OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
+                "RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA")
+                .setPermission(Manifest.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA)
+                .setDefaultMode(AppOpsManager.MODE_DEFAULT).build()
     };
 
     // The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java
new file mode 100644
index 0000000..9828133
--- /dev/null
+++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.view.Display.INVALID_DISPLAY;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ClientTransactionHandler;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.util.MergedConfiguration;
+import android.view.IWindow;
+import android.view.InsetsState;
+import android.window.ClientWindowFrames;
+
+import java.util.Objects;
+
+/**
+ * Message to deliver window resize info.
+ * @hide
+ */
+public class WindowStateResizeItem extends ClientTransactionItem {
+
+    private IWindow mWindow;
+    private ClientWindowFrames mFrames;
+    private boolean mReportDraw;
+    private MergedConfiguration mConfiguration;
+    private InsetsState mInsetsState;
+    private boolean mForceLayout;
+    private boolean mAlwaysConsumeSystemBars;
+    private int mDisplayId;
+    private int mSyncSeqId;
+    private boolean mDragResizing;
+
+    @Override
+    public void execute(@NonNull ClientTransactionHandler client,
+            @NonNull PendingTransactionActions pendingActions) {
+        try {
+            mWindow.resized(mFrames, mReportDraw, mConfiguration, mInsetsState, mForceLayout,
+                    mAlwaysConsumeSystemBars, mDisplayId, mSyncSeqId, mDragResizing);
+        } catch (RemoteException e) {
+            // Should be a local call.
+            throw new RuntimeException(e);
+        }
+    }
+
+    // ObjectPoolItem implementation
+
+    private WindowStateResizeItem() {}
+
+    /** Obtains an instance initialized with provided params. */
+    public static WindowStateResizeItem obtain(@NonNull IWindow window,
+            @NonNull ClientWindowFrames frames, boolean reportDraw,
+            @NonNull MergedConfiguration configuration, @NonNull InsetsState insetsState,
+            boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
+            boolean dragResizing) {
+        WindowStateResizeItem instance =
+                ObjectPool.obtain(WindowStateResizeItem.class);
+        if (instance == null) {
+            instance = new WindowStateResizeItem();
+        }
+        instance.mWindow = requireNonNull(window);
+        instance.mFrames = requireNonNull(frames);
+        instance.mReportDraw = reportDraw;
+        instance.mConfiguration = requireNonNull(configuration);
+        instance.mInsetsState = requireNonNull(insetsState);
+        instance.mForceLayout = forceLayout;
+        instance.mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
+        instance.mDisplayId = displayId;
+        instance.mSyncSeqId = syncSeqId;
+        instance.mDragResizing = dragResizing;
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        mWindow = null;
+        mFrames = null;
+        mReportDraw = false;
+        mConfiguration = null;
+        mInsetsState = null;
+        mForceLayout = false;
+        mAlwaysConsumeSystemBars = false;
+        mDisplayId = INVALID_DISPLAY;
+        mSyncSeqId = -1;
+        mDragResizing = false;
+        ObjectPool.recycle(this);
+    }
+
+    // Parcelable implementation
+
+    /** Writes to Parcel. */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mWindow.asBinder());
+        dest.writeTypedObject(mFrames, flags);
+        dest.writeBoolean(mReportDraw);
+        dest.writeTypedObject(mConfiguration, flags);
+        dest.writeTypedObject(mInsetsState, flags);
+        dest.writeBoolean(mForceLayout);
+        dest.writeBoolean(mAlwaysConsumeSystemBars);
+        dest.writeInt(mDisplayId);
+        dest.writeInt(mSyncSeqId);
+        dest.writeBoolean(mDragResizing);
+    }
+
+    /** Reads from Parcel. */
+    private WindowStateResizeItem(@NonNull Parcel in) {
+        mWindow = IWindow.Stub.asInterface(in.readStrongBinder());
+        mFrames = in.readTypedObject(ClientWindowFrames.CREATOR);
+        mReportDraw = in.readBoolean();
+        mConfiguration = in.readTypedObject(MergedConfiguration.CREATOR);
+        mInsetsState = in.readTypedObject(InsetsState.CREATOR);
+        mForceLayout = in.readBoolean();
+        mAlwaysConsumeSystemBars = in.readBoolean();
+        mDisplayId = in.readInt();
+        mSyncSeqId = in.readInt();
+        mDragResizing = in.readBoolean();
+    }
+
+    public static final @NonNull Creator<WindowStateResizeItem> CREATOR = new Creator<>() {
+        public WindowStateResizeItem createFromParcel(@NonNull Parcel in) {
+            return new WindowStateResizeItem(in);
+        }
+
+        public WindowStateResizeItem[] newArray(int size) {
+            return new WindowStateResizeItem[size];
+        }
+    };
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final WindowStateResizeItem other = (WindowStateResizeItem) o;
+        return Objects.equals(mWindow, other.mWindow)
+                && Objects.equals(mFrames, other.mFrames)
+                && mReportDraw == other.mReportDraw
+                && Objects.equals(mConfiguration, other.mConfiguration)
+                && Objects.equals(mInsetsState, other.mInsetsState)
+                && mForceLayout == other.mForceLayout
+                && mAlwaysConsumeSystemBars == other.mAlwaysConsumeSystemBars
+                && mDisplayId == other.mDisplayId
+                && mSyncSeqId == other.mSyncSeqId
+                && mDragResizing == other.mDragResizing;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + Objects.hashCode(mWindow);
+        result = 31 * result + Objects.hashCode(mFrames);
+        result = 31 * result + (mReportDraw ? 1 : 0);
+        result = 31 * result + Objects.hashCode(mConfiguration);
+        result = 31 * result + Objects.hashCode(mInsetsState);
+        result = 31 * result + (mForceLayout ? 1 : 0);
+        result = 31 * result + (mAlwaysConsumeSystemBars ? 1 : 0);
+        result = 31 * result + mDisplayId;
+        result = 31 * result + mSyncSeqId;
+        result = 31 * result + (mDragResizing ? 1 : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "WindowStateResizeItem{window=" + mWindow
+                + ", reportDrawn=" + mReportDraw
+                + ", configuration=" + mConfiguration
+                + "}";
+    }
+}
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 083fa00..6393c45 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -15,6 +15,7 @@
  */
 package android.companion;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -412,6 +413,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_NEW_ASSOCIATION_BUILDER)
     @TestApi
     public static final class Builder {
         private final int mId;
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 03e75e9..570ecaa 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -161,16 +161,16 @@
     public static final int DEVICE_EVENT_BT_DISCONNECTED = 3;
 
     /**
-     * A companion app for a {@link AssociationInfo#isSelfManaged() self-managed} device will
-     * receive the callback {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a
-     * device has appeared on its own.
+     * A companion app for a self-managed device will receive the callback
+     * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has appeared on its
+     * own.
      */
     public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4;
 
     /**
-     * A companion app for a {@link AssociationInfo#isSelfManaged() self-managed} device will
-     * receive the callback {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a
-     * device has disappeared on its own.
+     * A companion app for a self-managed device will receive the callback
+     * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has disappeared on
+     * its own.
      */
     public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5;
 
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
new file mode 100644
index 0000000..b9e5609
--- /dev/null
+++ b/core/java/android/companion/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.companion"
+
+flag {
+    name: "new_association_builder"
+    namespace: "companion"
+    description: "Controls if the new Builder is exposed to test apis."
+    bug: "296251481"
+}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index 0af4c92..93a3e78 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -33,9 +33,6 @@
  *
  * <p>Read-only device representation exposing the properties of an existing virtual device.
  *
- * <p class="note">Not to be confused with {@link VirtualDeviceManager.VirtualDevice}, which is used
- * by the virtual device creator and allows them to manage the device.
- *
  * @see VirtualDeviceManager#registerVirtualDeviceListener
  */
 public final class VirtualDevice implements Parcelable {
@@ -120,8 +117,6 @@
 
     /**
      * Returns the name of the virtual device (optionally) provided during its creation.
-     *
-     * @see VirtualDeviceParams.Builder#setName(String)
      */
     public @Nullable String getName() {
         return mName;
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
index bf78dd0..b9451a7 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
@@ -46,15 +46,15 @@
  * <pre>
  * VirtualSensorDirectChannelWriter writer = new VirtualSensorDirectChannelWriter();
  * VirtualSensorDirectChannelCallback callback = new VirtualSensorDirectChannelCallback() {
- *     @Override
+ *     {@literal @}Override
  *     public void onDirectChannelCreated(int channelHandle, SharedMemory sharedMemory) {
  *         writer.addChannel(channelHandle, sharedMemory);
  *     }
- *     @Override
+ *     {@literal @}Override
  *     public void onDirectChannelDestroyed(int channelHandle);
  *         writer.removeChannel(channelHandle);
  *     }
- *     @Override
+ *     {@literal @}Override
  *     public void onDirectChannelConfigured(int channelHandle, VirtualSensor sensor, int rateLevel,
  *             int reportToken)
  *         if (!writer.configureChannel(channelHandle, sensor, rateLevel, reportToken)) {
diff --git a/core/java/android/content/ContentCaptureOptions.java b/core/java/android/content/ContentCaptureOptions.java
index 36e0529..3fbcd70 100644
--- a/core/java/android/content/ContentCaptureOptions.java
+++ b/core/java/android/content/ContentCaptureOptions.java
@@ -30,6 +30,11 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 /**
  * Content capture options for a given package.
@@ -119,7 +124,10 @@
                 /* enableReceiver= */ false,
                 new ContentProtectionOptions(
                         /* enableReceiver= */ false,
-                        /* bufferSize= */ 0),
+                        /* bufferSize= */ 0,
+                        /* requiredGroups= */ Collections.emptyList(),
+                        /* optionalGroups= */ Collections.emptyList(),
+                        /* optionalGroupsThreshold= */ 0),
                 /* whitelistedComponents= */ null);
     }
 
@@ -141,9 +149,7 @@
                 logHistorySize,
                 ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
                 ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER,
-                new ContentProtectionOptions(
-                        ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER,
-                        ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE),
+                new ContentProtectionOptions(),
                 whitelistedComponents);
     }
 
@@ -183,9 +189,7 @@
                 ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE,
                 ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
                 ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER,
-                new ContentProtectionOptions(
-                        ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER,
-                        ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE),
+                new ContentProtectionOptions(),
                 whitelistedComponents);
     }
 
@@ -386,9 +390,58 @@
          */
         public final int bufferSize;
 
-        public ContentProtectionOptions(boolean enableReceiver, int bufferSize) {
+        /**
+         * The list of required groups of strings to match.
+         *
+         * @hide
+         */
+        @NonNull public final List<List<String>> requiredGroups;
+
+        /**
+         * The list of optional groups of strings to match.
+         *
+         * @hide
+         */
+        @NonNull public final List<List<String>> optionalGroups;
+
+        /**
+         * The minimal number of optional groups that have to be matched. This is the threshold
+         * value and comparison is done with greater than or equals.
+         *
+         * @hide
+         */
+        public final int optionalGroupsThreshold;
+
+        /**
+         * Empty constructor with default values.
+         *
+         * @hide
+         */
+        public ContentProtectionOptions() {
+            this(
+                    ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER,
+                    ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE,
+                    ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS,
+                    ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS,
+                    ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD);
+        }
+
+        /**
+         * Full primary constructor.
+         *
+         * @hide
+         */
+        public ContentProtectionOptions(
+                boolean enableReceiver,
+                int bufferSize,
+                @NonNull List<List<String>> requiredGroups,
+                @NonNull List<List<String>> optionalGroups,
+                int optionalGroupsThreshold) {
             this.enableReceiver = enableReceiver;
             this.bufferSize = bufferSize;
+            this.requiredGroups = requiredGroups;
+            this.optionalGroups = optionalGroups;
+            this.optionalGroupsThreshold = optionalGroupsThreshold;
         }
 
         @Override
@@ -398,7 +451,14 @@
                     .append("enableReceiver=")
                     .append(enableReceiver)
                     .append(", bufferSize=")
-                    .append(bufferSize);
+                    .append(bufferSize)
+                    .append(", requiredGroupsSize=")
+                    .append(requiredGroups.size())
+                    .append(", optionalGroupsSize=")
+                    .append(optionalGroups.size())
+                    .append(", optionalGroupsThreshold=")
+                    .append(optionalGroupsThreshold);
+
             return stringBuilder.append(']').toString();
         }
 
@@ -407,17 +467,50 @@
             pw.print(enableReceiver);
             pw.print(", bufferSize=");
             pw.print(bufferSize);
+            pw.print(", requiredGroupsSize=");
+            pw.print(requiredGroups.size());
+            pw.print(", optionalGroupsSize=");
+            pw.print(optionalGroups.size());
+            pw.print(", optionalGroupsThreshold=");
+            pw.print(optionalGroupsThreshold);
         }
 
-        private void writeToParcel(Parcel parcel) {
+        private void writeToParcel(@NonNull Parcel parcel) {
             parcel.writeBoolean(enableReceiver);
             parcel.writeInt(bufferSize);
+            writeGroupsToParcel(requiredGroups, parcel);
+            writeGroupsToParcel(optionalGroups, parcel);
+            parcel.writeInt(optionalGroupsThreshold);
         }
 
-        private static ContentProtectionOptions createFromParcel(Parcel parcel) {
+        @NonNull
+        private static ContentProtectionOptions createFromParcel(@NonNull Parcel parcel) {
             boolean enableReceiver = parcel.readBoolean();
             int bufferSize = parcel.readInt();
-            return new ContentProtectionOptions(enableReceiver, bufferSize);
+            List<List<String>> requiredGroups = createGroupsFromParcel(parcel);
+            List<List<String>> optionalGroups = createGroupsFromParcel(parcel);
+            int optionalGroupsThreshold = parcel.readInt();
+            return new ContentProtectionOptions(
+                    enableReceiver,
+                    bufferSize,
+                    requiredGroups,
+                    optionalGroups,
+                    optionalGroupsThreshold);
+        }
+
+        private static void writeGroupsToParcel(
+                @NonNull List<List<String>> groups, @NonNull Parcel parcel) {
+            parcel.writeInt(groups.size());
+            groups.forEach(parcel::writeStringList);
+        }
+
+        @NonNull
+        private static List<List<String>> createGroupsFromParcel(@NonNull Parcel parcel) {
+            int size = parcel.readInt();
+            return IntStream.range(0, size)
+                    .mapToObj(i -> new ArrayList<String>())
+                    .peek(parcel::readStringList)
+                    .collect(Collectors.toUnmodifiableList());
         }
     }
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 45338bb..4d5d056 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4605,6 +4605,16 @@
     public static final String FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS =
             "android.software.wallet_location_based_suggestions";
 
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+     * the rotary encoder hardware to support rotating bezel on watch.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_ROTARY_ENCODER_LOW_RES =
+            "android.hardware.rotaryencoder.lowres";
+
     /** @hide */
     public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
 
diff --git a/core/java/android/credentials/CreateCredentialException.java b/core/java/android/credentials/CreateCredentialException.java
index c344004..8f07d19 100644
--- a/core/java/android/credentials/CreateCredentialException.java
+++ b/core/java/android/credentials/CreateCredentialException.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.Activity;
+import android.content.Context;
 import android.os.CancellationSignal;
 import android.os.OutcomeReceiver;
 
@@ -28,8 +28,8 @@
 
 /**
  * Represents an error encountered during the
- * {@link CredentialManager#createCredential(CreateCredentialRequest,
- * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation.
+ * {@link CredentialManager#createCredential(Context, CreateCredentialRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} operation.
  */
 public class CreateCredentialException extends Exception {
     /**
@@ -41,7 +41,7 @@
 
     /**
      * The error type value for when no create options are available from any provider(s),
-     * for the given {@link CredentialManager#createCredential(CreateCredentialRequest, Activity,
+     * for the given {@link CredentialManager#createCredential(Context, CreateCredentialRequest,
      * CancellationSignal, Executor, OutcomeReceiver)} request.
      */
     @NonNull
diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java
index db71624..755e659 100644
--- a/core/java/android/credentials/CredentialDescription.java
+++ b/core/java/android/credentials/CredentialDescription.java
@@ -155,8 +155,7 @@
     }
 
     /**
-     * {@link CredentialDescription#mType} and
-     * {@link CredentialDescription#mSupportedElementKeys} are enough for hashing. Constructor
+     * {@link #getType()} and {@link #getSupportedElementKeys()} are enough for hashing. Constructor
      * enforces {@link CredentialEntry} to have the same type and
      * {@link android.app.slice.Slice} contained by the entry can not be hashed.
      */
@@ -166,8 +165,7 @@
     }
 
     /**
-     * {@link CredentialDescription#mType} and
-     * {@link CredentialDescription#mSupportedElementKeys} are enough for equality check.
+     * {@link #getType()} and {@link #getSupportedElementKeys()} are enough for equality check.
      */
     @Override
     public boolean equals(Object obj) {
diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java
index 95ca011..8503072 100644
--- a/core/java/android/credentials/CredentialProviderInfo.java
+++ b/core/java/android/credentials/CredentialProviderInfo.java
@@ -16,12 +16,14 @@
 
 package android.credentials;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ServiceInfo;
+import android.credentials.flags.Flags;
 import android.graphics.drawable.Drawable;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -42,6 +44,7 @@
     @NonNull private final List<String> mCapabilities = new ArrayList<>();
     @Nullable private final CharSequence mOverrideLabel;
     @Nullable private CharSequence mSettingsSubtitle = null;
+    @Nullable private CharSequence mSettingsActivity = null;
     private final boolean mIsSystemProvider;
     private final boolean mIsEnabled;
     private final boolean mIsPrimary;
@@ -59,6 +62,7 @@
         mIsEnabled = builder.mIsEnabled;
         mIsPrimary = builder.mIsPrimary;
         mOverrideLabel = builder.mOverrideLabel;
+        mSettingsActivity = builder.mSettingsActivity;
     }
 
     /** Returns true if the service supports the given {@code credentialType}, false otherwise. */
@@ -104,10 +108,7 @@
         return mIsEnabled;
     }
 
-    /**
-     * Returns whether the provider is set as primary by the user.
-     *
-     */
+    /** Returns whether the provider is set as primary by the user. */
     public boolean isPrimary() {
         return mIsPrimary;
     }
@@ -118,6 +119,18 @@
         return mSettingsSubtitle;
     }
 
+    /**
+     * Returns the settings activity.
+     *
+     * @hide
+     */
+    @Nullable
+    @TestApi
+    @FlaggedApi(Flags.FLAG_SETTINGS_ACTIVITY_ENABLED)
+    public CharSequence getSettingsActivity() {
+        return mSettingsActivity;
+    }
+
     /** Returns the component name for the service. */
     @NonNull
     public ComponentName getComponentName() {
@@ -133,6 +146,7 @@
         dest.writeBoolean(mIsPrimary);
         TextUtils.writeToParcel(mOverrideLabel, dest, flags);
         TextUtils.writeToParcel(mSettingsSubtitle, dest, flags);
+        TextUtils.writeToParcel(mSettingsActivity, dest, flags);
     }
 
     @Override
@@ -161,6 +175,9 @@
                 + "settingsSubtitle="
                 + mSettingsSubtitle
                 + ", "
+                + "settingsActivity="
+                + mSettingsActivity
+                + ", "
                 + "capabilities="
                 + String.join(",", mCapabilities)
                 + "}";
@@ -174,6 +191,7 @@
         mIsPrimary = in.readBoolean();
         mOverrideLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mSettingsSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mSettingsActivity = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
     }
 
     public static final @NonNull Parcelable.Creator<CredentialProviderInfo> CREATOR =
@@ -196,6 +214,7 @@
         @NonNull private List<String> mCapabilities = new ArrayList<>();
         private boolean mIsSystemProvider = false;
         @Nullable private CharSequence mSettingsSubtitle = null;
+        @Nullable private CharSequence mSettingsActivity = null;
         private boolean mIsEnabled = false;
         private boolean mIsPrimary = false;
         @Nullable private CharSequence mOverrideLabel = null;
@@ -231,6 +250,16 @@
             return this;
         }
 
+        /**
+         * Sets the settings activity.
+         *
+         * @hide
+         */
+        public @NonNull Builder setSettingsActivity(@Nullable CharSequence settingsActivity) {
+            mSettingsActivity = settingsActivity;
+            return this;
+        }
+
         /** Sets a list of capabilities this provider service can support. */
         public @NonNull Builder addCapabilities(@NonNull List<String> capabilities) {
             mCapabilities.addAll(capabilities);
diff --git a/core/java/android/credentials/GetCredentialException.java b/core/java/android/credentials/GetCredentialException.java
index 720c53b..0421d1f 100644
--- a/core/java/android/credentials/GetCredentialException.java
+++ b/core/java/android/credentials/GetCredentialException.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.Activity;
+import android.content.Context;
 import android.os.CancellationSignal;
 import android.os.OutcomeReceiver;
 
@@ -28,8 +28,8 @@
 
 /**
  * Represents an error encountered during the
- * {@link CredentialManager#getCredential(GetCredentialRequest,
- * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation.
+ * {@link CredentialManager#getCredential(Context, GetCredentialRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} operation.
  */
 public class GetCredentialException extends Exception {
     /**
@@ -41,7 +41,7 @@
 
     /**
      * The error type value for when no credential is found available for the given {@link
-     * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal,
+     * CredentialManager#getCredential(Context, GetCredentialRequest, CancellationSignal,
      * Executor, OutcomeReceiver)} request.
      */
     @NonNull
diff --git a/core/java/android/credentials/PrepareGetCredentialResponse.java b/core/java/android/credentials/PrepareGetCredentialResponse.java
index 056b18a..212f571 100644
--- a/core/java/android/credentials/PrepareGetCredentialResponse.java
+++ b/core/java/android/credentials/PrepareGetCredentialResponse.java
@@ -22,7 +22,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.app.Activity;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.IntentSender;
@@ -36,7 +35,7 @@
 /**
  * A response object that prefetches user app credentials and provides metadata about them. It can
  * then be used to issue the full credential retrieval flow via the
- * {@link CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal,
+ * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal,
  * Executor, OutcomeReceiver)} method to perform the remaining flows such as consent collection
  * and credential selection, to officially retrieve a credential.
  */
@@ -44,7 +43,7 @@
 
     /**
      * A handle that represents a pending get-credential operation. Pass this handle to {@link
-     * CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal,
+     * CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal,
      * Executor, OutcomeReceiver)} to perform the remaining flows to officially retrieve a
      * credential.
      */
@@ -144,7 +143,7 @@
 
     /**
      * Returns a handle that represents this pending get-credential operation. Pass this handle to
-     * {@link CredentialManager#getCredential(PendingGetCredentialHandle, Activity,
+     * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle,
      * CancellationSignal, Executor, OutcomeReceiver)} to perform the remaining flows to officially
      * retrieve a credential.
      */
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 746f2f2..b003e75 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -16,6 +16,7 @@
 
 package android.database.sqlite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -700,6 +701,7 @@
      *   }
      * </pre>
      */
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
     public void beginTransactionReadOnly() {
         beginTransactionWithListenerReadOnly(null);
     }
@@ -783,6 +785,7 @@
      *   }
      * </pre>
      */
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
     public void beginTransactionWithListenerReadOnly(
             @Nullable SQLiteTransactionListener transactionListener) {
         beginTransaction(transactionListener, SQLiteSession.TRANSACTION_MODE_DEFERRED);
@@ -2221,6 +2224,7 @@
      * @throws IllegalStateException if a transaction is not in progress.
      * @throws SQLiteException if the SQL cannot be compiled.
      */
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
     @NonNull
     public SQLiteRawStatement createRawStatement(@NonNull String sql) {
         Objects.requireNonNull(sql);
@@ -2240,6 +2244,7 @@
      * @return The ROWID of the last row to be inserted under this connection.
      * @throws IllegalStateException if there is no current transaction.
      */
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
     public long getLastInsertRowId() {
         return getThreadSession().getLastInsertRowId();
     }
@@ -2253,6 +2258,7 @@
      * @return The number of rows changed by the most recent sql statement
      * @throws IllegalStateException if there is no current transaction.
      */
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
     public long getLastChangedRowCount() {
         return getThreadSession().getLastChangedRowCount();
     }
@@ -2280,6 +2286,7 @@
      * @return The number of rows changed on the current connection.
      * @throws IllegalStateException if there is no current transaction.
      */
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
     public long getTotalChangedRowCount() {
         return getThreadSession().getTotalChangedRowCount();
     }
diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java
index 165f181..827420f 100644
--- a/core/java/android/database/sqlite/SQLiteRawStatement.java
+++ b/core/java/android/database/sqlite/SQLiteRawStatement.java
@@ -16,6 +16,7 @@
 
 package android.database.sqlite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -70,6 +71,7 @@
  *
  * @see <a href="http://sqlite.org/c3ref/stmt.html">sqlite3_stmt</a>
  */
+@FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
 public final class SQLiteRawStatement implements Closeable {
 
     private static final String TAG = "SQLiteRawStatement";
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
new file mode 100644
index 0000000..564df03
--- /dev/null
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.database.sqlite"
+
+flag {
+     name: "sqlite_apis_15"
+     namespace: "system_performance"
+     is_fixed_read_only: true
+     description: "SQLite APIs held back for Android 15"
+     bug: "279043253"
+}
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 82694ee..9c05dfc 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -537,6 +537,24 @@
     }
 
     /**
+     * Listens for biometric prompt status, i.e., if it is being shown or idle.
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void registerBiometricPromptStatusListener(
+            IBiometricPromptStatusListener callback) {
+        if (mService != null) {
+            try {
+                mService.registerBiometricPromptStatusListener(callback);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            Slog.w(TAG, "registerBiometricPromptOnKeyguardCallback(): Service not connected");
+        }
+    }
+
+    /**
      * Requests all {@link Authenticators.Types#BIOMETRIC_STRONG} sensors to have their
      * authenticatorId invalidated for the specified user. This happens when enrollments have been
      * added on devices with multiple biometric sensors.
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index c2e5c0b..8eede47 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -17,6 +17,7 @@
 package android.hardware.biometrics;
 
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.IBiometricPromptStatusListener;
 import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
@@ -63,6 +64,9 @@
     // Register callback for when keyguard biometric eligibility changes.
     void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);
 
+    // Register callback to check biometric prompt status.
+    void registerBiometricPromptStatusListener(IBiometricPromptStatusListener callback);
+
     // Requests all BIOMETRIC_STRONG sensors to have their authenticatorId invalidated for the
     // specified user. This happens when enrollments have been added on devices with multiple
     // biometric sensors.
diff --git a/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl b/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl
new file mode 100644
index 0000000..7a0f438
--- /dev/null
+++ b/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.biometrics;
+
+/**
+ * Communication channel to propagate biometric prompt status. Implementation of this interface
+ * should be registered in BiometricService#registerBiometricPromptStatusListener.
+ * @hide
+ */
+oneway interface IBiometricPromptStatusListener {
+    void onBiometricPromptShowing();
+    void onBiometricPromptIdle();
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 18c8d1b..36606a1 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -17,6 +17,7 @@
 package android.hardware.biometrics;
 
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.IBiometricPromptStatusListener;
 import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IInvalidationCallback;
@@ -68,6 +69,10 @@
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);
 
+    // Register a callback for biometric prompt status on keyguard.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    void registerBiometricPromptStatusListener(IBiometricPromptStatusListener callback);
+
     // Notify BiometricService when <Biometric>Service is ready to start the prepared client.
     // Client lifecycle is still managed in <Biometric>Service.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
diff --git a/core/java/android/os/PowerMonitor.java b/core/java/android/os/PowerMonitor.java
index 5fb0df7..8c5f2cd 100644
--- a/core/java/android/os/PowerMonitor.java
+++ b/core/java/android/os/PowerMonitor.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 
@@ -23,12 +24,19 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * A PowerMonitor represents either a Channel aka ODPM rail (on-device power monitor) or an
- * EnergyConsumer, as defined in
- * <a href="https://android.googlesource.com/platform/hardware/interfaces/+/refs/heads/main/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats">android.hardware.power.stats</a>
- *
- * @hide
+ * A PowerMonitor represents either an ODPM rail (on-device power rail monitor) or a modeled
+ * energy consumer.
+ * <p/>
+ * ODPM rail names are device-specific. No assumptions should be made about the names and
+ * exact purpose of ODPM rails across different device models. A rail name may be something
+ * like "S2S_VDD_G3D"; specific knowledge of the device hardware is required to interpret
+ * the corresponding power monitor data.
+ * <p/>
+ * Energy consumer have more human-readable names, e.g. "GPU", "MODEM" etc. However, developers
+ * must be extra cautious about using energy consumers across different device models,
+ * as their exact implementations are also hardware dependent and are customized by OEMs.
  */
+@FlaggedApi("com.android.server.power.optimization.power_monitor_api")
 public final class PowerMonitor implements Parcelable {
 
     /**
@@ -36,9 +44,9 @@
      * power rail measurement, or modeled in some fashion.  For example, an energy consumer may
      * represent a combination of multiple rails or a portion of a rail shared between subsystems,
      * e.g. WiFi and Bluetooth are often handled by the same chip, powered by a shared rail.
-     * Some consumer names are standardized (see android.hardware.power.stats.EnergyConsumerType),
-     * others are not.
+     * Some consumer names are standardized, others are not.
      */
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     public static final int POWER_MONITOR_TYPE_CONSUMER = 0;
 
     /**
@@ -46,6 +54,7 @@
      * no assumptions can be made about the source of those measurements across different devices,
      * even if they have the same name.
      */
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     public static final int POWER_MONITOR_TYPE_MEASUREMENT = 1;
 
     /** @hide */
@@ -64,38 +73,60 @@
      * @hide
      */
     public final int index;
+
     @PowerMonitorType
-    public final int type;
+    private final int mType;
     @NonNull
-    public final String name;
+    private final String mName;
 
     /**
      * @hide
      */
     public PowerMonitor(int index, int type, @NonNull String name) {
         this.index = index;
-        this.type = type;
-        this.name = name;
+        this.mType = type;
+        this.mName = name;
+    }
+
+    /**
+     * Returns the type of the power monitor.
+     */
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
+    @PowerMonitorType
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the name of the power monitor, either a power rail or an energy consumer.
+     */
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
+    @NonNull
+    public String getName() {
+        return mName;
     }
 
     private PowerMonitor(Parcel in) {
         index = in.readInt();
-        type = in.readInt();
-        name = in.readString();
+        mType = in.readInt();
+        mName = in.readString8();
     }
 
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(index);
-        dest.writeInt(type);
-        dest.writeString(name);
+        dest.writeInt(mType);
+        dest.writeString8(mName);
     }
 
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     @Override
     public int describeContents() {
         return 0;
     }
 
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     @NonNull
     public static final Creator<PowerMonitor> CREATOR = new Creator<>() {
         @Override
diff --git a/core/java/android/os/PowerMonitorReadings.java b/core/java/android/os/PowerMonitorReadings.java
index e767059..bb677d5 100644
--- a/core/java/android/os/PowerMonitorReadings.java
+++ b/core/java/android/os/PowerMonitorReadings.java
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.annotation.ElapsedRealtimeLong;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 
 import java.util.Arrays;
@@ -24,10 +25,10 @@
 
 /**
  * A collection of energy measurements from Power Monitors.
- *
- * @hide
  */
+@FlaggedApi("com.android.server.power.optimization.power_monitor_api")
 public final class PowerMonitorReadings {
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     public static final int ENERGY_UNAVAILABLE = -1;
 
     @NonNull
@@ -41,7 +42,7 @@
             Comparator.comparingInt(pm -> pm.index);
 
     /**
-     * @param powerMonitors array of power monitor (ODPM) rails, sorted by PowerMonitor.index
+     * @param powerMonitors array of power monitor, sorted by PowerMonitor.index
      * @hide
      */
     public PowerMonitorReadings(@NonNull PowerMonitor[] powerMonitors,
@@ -56,7 +57,8 @@
      * Does not persist across reboots.
      * Represents total energy: both on-battery and plugged-in.
      */
-    public long getConsumedEnergyUws(@NonNull PowerMonitor powerMonitor) {
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
+    public long getConsumedEnergy(@NonNull PowerMonitor powerMonitor) {
         int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR);
         if (offset >= 0) {
             return mEnergyUws[offset];
@@ -67,6 +69,7 @@
     /**
      * Elapsed realtime, in milliseconds, when the snapshot was taken.
      */
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     @ElapsedRealtimeLong
     public long getTimestamp(@NonNull PowerMonitor powerMonitor) {
         int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR);
@@ -84,7 +87,7 @@
             if (i != 0) {
                 sb.append(", ");
             }
-            sb.append(mPowerMonitors[i].name)
+            sb.append(mPowerMonitors[i].getName())
                     .append(" = ").append(mEnergyUws[i])
                     .append(" (").append(mTimestampsMs[i]).append(')');
         }
diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java
index dfc43f4..2d53341 100644
--- a/core/java/android/os/health/SystemHealthManager.java
+++ b/core/java/android/os/health/SystemHealthManager.java
@@ -16,6 +16,7 @@
 
 package android.os.health;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
@@ -24,7 +25,6 @@
 import android.os.BatteryStats;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.IPowerStatsService;
 import android.os.PowerMonitor;
@@ -157,39 +157,15 @@
     }
 
     /**
-     * Returns a list of supported power monitors, which include raw ODPM rails and
-     * modeled energy consumers.  If ODPM is unsupported by PowerStats HAL, this method returns
-     * an empty array.
+     * Asynchronously retrieves a list of supported  {@link PowerMonitor}'s, which include raw ODPM
+     * (on-device power rail monitor) rails and modeled energy consumers.  If ODPM is unsupported
+     * on this device this method delivers an empty list.
      *
-     * @hide
-     */
-    @NonNull
-    public List<PowerMonitor> getSupportedPowerMonitors() {
-        synchronized (mPowerMonitorsLock) {
-            if (mPowerMonitorsInfo != null) {
-                return mPowerMonitorsInfo;
-            }
-        }
-        ConditionVariable lock = new ConditionVariable();
-        // Populate mPowerMonitorsInfo by side-effect
-        getSupportedPowerMonitors(null, unused -> lock.open());
-        lock.block();
-
-        synchronized (mPowerMonitorsLock) {
-            return mPowerMonitorsInfo;
-        }
-    }
-
-    /**
-     * Asynchronously retrieves a list of supported power monitors, see
-     * {@link #getSupportedPowerMonitors()}
-     *
-     * @param handler optional Handler to deliver the callback. If not supplied, the callback
-     *                may be invoked on an arbitrary thread.
+     * @param handler  optional Handler to deliver the callback. If not supplied, the callback
+     *                 may be invoked on an arbitrary thread.
      * @param onResult callback for the result
-     *
-     * @hide
      */
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     public void getSupportedPowerMonitors(@Nullable Handler handler,
             @NonNull Consumer<List<PowerMonitor>> onResult) {
         final List<PowerMonitor> result;
@@ -229,35 +205,6 @@
         }
     }
 
-    /**
-     * Retrieves the accumulated power consumption reported by the specified power monitors.
-     *
-     * @param powerMonitors power monitors to be returned.
-     *
-     * @hide
-     */
-    @NonNull
-    public PowerMonitorReadings getPowerMonitorReadings(@NonNull List<PowerMonitor> powerMonitors) {
-        PowerMonitorReadings[] outReadings = new PowerMonitorReadings[1];
-        RuntimeException[] outException = new RuntimeException[1];
-        ConditionVariable lock = new ConditionVariable();
-        getPowerMonitorReadings(powerMonitors, null,
-                pms -> {
-                    outReadings[0] = pms;
-                    lock.open();
-                },
-                error -> {
-                    outException[0] = error;
-                    lock.open();
-                }
-        );
-        lock.block();
-        if (outException[0] != null) {
-            throw outException[0];
-        }
-        return outReadings[0];
-    }
-
     private static final Comparator<PowerMonitor> POWER_MONITOR_COMPARATOR =
             Comparator.comparingInt(pm -> pm.index);
 
@@ -270,9 +217,8 @@
      *                      may be invoked on an arbitrary thread.
      * @param onSuccess     callback for the result
      * @param onError       callback invoked in case of an error
-     *
-     * @hide
      */
+    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     public void getPowerMonitorReadings(@NonNull List<PowerMonitor> powerMonitors,
             @Nullable Handler handler, @NonNull Consumer<PowerMonitorReadings> onSuccess,
             @NonNull Consumer<RuntimeException> onError) {
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 88f62f3..66ad12c 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -27,3 +27,13 @@
     description: "Enables the APIs for vibration serialization/deserialization."
     bug: "245129509"
 }
+
+flag {
+    namespace: "haptics"
+    name: "haptic_feedback_vibration_oem_customization_enabled"
+    description: "Enables OEMs/devices to customize vibrations for haptic feedback"
+    # Make read only. This is because the flag is used only once, and this could happen before
+    # the read-write flag values propagate to the device.
+    is_fixed_read_only: true
+    bug: "291128479"
+}
diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
index df93433..5d96f69 100644
--- a/core/java/android/service/credentials/BeginCreateCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
@@ -143,7 +143,7 @@
          *
          * <p> Note that as a provider service you will only be able to set a remote entry if :
          * - Provider service possesses the
-         * {@link Manifest.permission.PROVIDE_REMOTE_CREDENTIALS} permission.
+         * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission.
          * - Provider service is configured as the provider that can provide remote entries.
          *
          * If the above conditions are not met, setting back {@link BeginCreateCredentialResponse}
diff --git a/core/java/android/service/credentials/BeginGetCredentialResponse.java b/core/java/android/service/credentials/BeginGetCredentialResponse.java
index 5ed06ac..ae6ca25 100644
--- a/core/java/android/service/credentials/BeginGetCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginGetCredentialResponse.java
@@ -160,7 +160,7 @@
          *
          * <p> Note that as a provider service you will only be able to set a remote entry if :
          * - Provider service possesses the
-         * {@link Manifest.permission.PROVIDE_REMOTE_CREDENTIALS} permission.
+         * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission.
          * - Provider service is configured as the provider that can provide remote entries.
          *
          * If the above conditions are not met, setting back {@link BeginGetCredentialResponse}
diff --git a/core/java/android/service/credentials/CallingAppInfo.java b/core/java/android/service/credentials/CallingAppInfo.java
index e755581..c3524c5 100644
--- a/core/java/android/service/credentials/CallingAppInfo.java
+++ b/core/java/android/service/credentials/CallingAppInfo.java
@@ -103,7 +103,7 @@
      * of other applications.
      *
      * Android system makes sure that only applications that poses the permission
-     * {@link android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on
+     * {@link android.Manifest.permission#CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on
      * the incoming {@link android.credentials.GetCredentialRequest} or
      * {@link android.credentials.CreateCredentialRequest}.
      */
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index 0aa6a23..514d722 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -45,8 +45,8 @@
 import android.util.Slog;
 import android.util.Xml;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -135,8 +135,8 @@
     }
 
     /**
-     * Constructs an information instance of the credential provider for testing purposes. Does
-     * not run any verifications and passes parameters as is.
+     * Constructs an information instance of the credential provider for testing purposes. Does not
+     * run any verifications and passes parameters as is.
      */
     @VisibleForTesting
     public static CredentialProviderInfo createForTests(
@@ -151,7 +151,6 @@
                 .setSystemProvider(isSystemProvider)
                 .addCapabilities(capabilities)
                 .build();
-
     }
 
     private static void verifyProviderPermission(ServiceInfo serviceInfo) throws SecurityException {
@@ -267,15 +266,21 @@
                                     allAttributes,
                                     com.android.internal.R.styleable.CredentialProvider);
                     builder.setSettingsSubtitle(
-                            afsAttributes.getString(
+                            getAfsAttributeSafe(
+                                    afsAttributes,
                                     R.styleable.CredentialProvider_settingsSubtitle));
+                    builder.setSettingsActivity(
+                            getAfsAttributeSafe(
+                                    afsAttributes,
+                                    R.styleable.CredentialProvider_settingsActivity));
                 } catch (Exception e) {
-                    Slog.e(TAG, "Failed to get XML attr", e);
+                    Slog.w(TAG, "Failed to get XML attr for metadata", e);
                 } finally {
                     if (afsAttributes != null) {
                         afsAttributes.recycle();
                     }
                 }
+
                 builder.addCapabilities(parseXmlProviderOuterCapabilities(parser, resources));
             } else {
                 Slog.w(TAG, "Meta-data does not start with credential-provider-service tag");
@@ -287,6 +292,21 @@
         return builder;
     }
 
+    private static @Nullable String getAfsAttributeSafe(
+            @Nullable TypedArray afsAttributes, int resId) {
+        if (afsAttributes == null) {
+            return null;
+        }
+
+        try {
+            return afsAttributes.getString(resId);
+        } catch (Exception e) {
+            Slog.w(TAG, "Failed to get XML attr from afs attributes", e);
+        }
+
+        return null;
+    }
+
     private static List<String> parseXmlProviderOuterCapabilities(
             XmlPullParser parser, Resources resources) throws IOException, XmlPullParserException {
         final List<String> capabilities = new ArrayList<>();
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index 79c8fb4..c82a4ca 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -26,6 +26,7 @@
 import android.system.OsConstants;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
 
@@ -138,6 +139,7 @@
      *
      * @hide
      */
+    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
     public final boolean isFdNotNullAndClosed() {
         return mRankingMapFd != null && mRankingMapFd.getFd() == -1;
     }
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index d554514..11180ae 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -54,6 +54,10 @@
      */
     void executeCommand(String command, String parameters, in ParcelFileDescriptor descriptor);
 
+    /**
+     * Please dispatch through WindowStateResizeItem instead of directly calling this method from
+     * the system server.
+     */
     void resized(in ClientWindowFrames frames, boolean reportDraw,
             in MergedConfiguration newMergedConfiguration, in InsetsState insetsState,
             boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId,
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index c35b690..9f886c8 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -778,7 +778,8 @@
      * Each gamepad or joystick is given a unique, positive controller number when initially
      * configured by the system. This number may change due to events such as device disconnects /
      * reconnects or user initiated reassignment. Any change in number will trigger an event that
-     * can be observed by registering an {@link InputManagerGlobal.InputDeviceListener}.
+     * can be observed by registering an
+     * {@link android.hardware.input.InputManager.InputDeviceListener}.
      * </p>
      * <p>
      * All input devices which are not gamepads or joysticks will be assigned a controller number
diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java
index 78716f5..0ba4148 100644
--- a/core/java/android/view/ScrollFeedbackProvider.java
+++ b/core/java/android/view/ScrollFeedbackProvider.java
@@ -17,7 +17,6 @@
 package android.view;
 
 import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
 import android.view.flags.Flags;
 
 /**
@@ -43,7 +42,8 @@
  * the scroll event. If calling this method in response to a {@link MotionEvent}, use the device ID
  * that is reported by the event, which can be obtained using {@link MotionEvent#getDeviceId()}.
  * Otherwise, use a valid ID that is obtained from {@link InputDevice#getId()}, or from an
- * {@link InputManager} instance ({@link InputManager#getInputDeviceIds()} gives all the valid input
+ * {@link android.hardware.input.InputManager} instance
+ * ({@link android.hardware.input.InputManager#getInputDeviceIds()} gives all the valid input
  * device IDs).
  *
  * <li><p><b>source</b>: should always be the {@link InputDevice} source that generated the scroll
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index b080b71..2f3d73a 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -263,7 +263,7 @@
     private static native void nativeSetDefaultFrameRateCompatibility(long transactionObj,
             long nativeObject, int compatibility);
     private static native void nativeSetFrameRateCategory(
-            long transactionObj, long nativeObject, int category);
+            long transactionObj, long nativeObject, int category, boolean smoothSwitchOnly);
     private static native void nativeSetFrameRateSelectionStrategy(
             long transactionObj, long nativeObject, int strategy);
     private static native long nativeGetHandle(long nativeObject);
@@ -3709,6 +3709,10 @@
          * @param sc The SurfaceControl to specify the frame rate category of.
          * @param category The frame rate category of this surface. The category value may influence
          * the system's choice of display frame rate.
+         * @param smoothSwitchOnly Set to {@code true} to indicate the display frame rate should not
+         * change if changing it would cause jank. Else {@code false}.
+         * This parameter is ignored when {@code category} is
+         * {@link Surface#FRAME_RATE_CATEGORY_DEFAULT}.
          *
          * @return This transaction object.
          *
@@ -3717,10 +3721,10 @@
          * @hide
          */
         @NonNull
-        public Transaction setFrameRateCategory(
-                @NonNull SurfaceControl sc, @Surface.FrameRateCategory int category) {
+        public Transaction setFrameRateCategory(@NonNull SurfaceControl sc,
+                @Surface.FrameRateCategory int category, boolean smoothSwitchOnly) {
             checkPreconditions(sc);
-            nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category);
+            nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category, smoothSwitchOnly);
             return this;
         }
 
@@ -4266,8 +4270,7 @@
          * be somewhat arbitrary, and so there are some somewhat arbitrary decisions in
          * this API as well.
          * <p>
-         * @param sc         The {@link SurfaceControl} to set the
-         *                   {@link TrustedPresentationCallback} on
+         * @param sc         The {@link SurfaceControl} to set the callback on
          * @param thresholds The {@link TrustedPresentationThresholds} that will specify when the to
          *                   invoke the callback.
          * @param executor   The {@link Executor} where the callback will be invoked on.
@@ -4300,10 +4303,9 @@
         }
 
         /**
-         * Clears the {@link TrustedPresentationCallback} for a specific {@link SurfaceControl}
+         * Clears the callback for a specific {@link SurfaceControl}
          *
-         * @param sc The SurfaceControl that the {@link TrustedPresentationCallback} should be
-         *           cleared from
+         * @param sc The SurfaceControl that the callback should be cleared from
          * @return This transaction
          */
         @NonNull
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e111dc8..b5648cc 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1730,7 +1730,7 @@
                         attrs.getTitle().toString());
                 mAttachInfo.mThreadedRenderer = renderer;
                 renderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue);
-                updateColorModeIfNeeded(attrs.getColorMode());
+                updateColorModeIfNeeded(attrs.getColorMode(), attrs.getDesiredHdrHeadroom());
                 updateRenderHdrSdrRatio();
                 updateForceDarkMode();
                 mAttachInfo.mHardwareAccelerated = true;
@@ -3349,7 +3349,7 @@
                 }
                 final boolean alwaysConsumeSystemBarsChanged =
                         mPendingAlwaysConsumeSystemBars != mAttachInfo.mAlwaysConsumeSystemBars;
-                updateColorModeIfNeeded(lp.getColorMode());
+                updateColorModeIfNeeded(lp.getColorMode(), lp.getDesiredHdrHeadroom());
                 surfaceCreated = !hadSurface && mSurface.isValid();
                 surfaceDestroyed = hadSurface && !mSurface.isValid();
 
@@ -5652,7 +5652,8 @@
         mUpdateHdrSdrRatioInfo = true;
     }
 
-    private void updateColorModeIfNeeded(@ActivityInfo.ColorMode int colorMode) {
+    private void updateColorModeIfNeeded(@ActivityInfo.ColorMode int colorMode,
+            float desiredRatio) {
         if (mAttachInfo.mThreadedRenderer == null) {
             return;
         }
@@ -5666,7 +5667,10 @@
                 && !getConfiguration().isScreenWideColorGamut()) {
             colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
         }
-        float desiredRatio = mAttachInfo.mThreadedRenderer.setColorMode(colorMode);
+        float automaticRatio = mAttachInfo.mThreadedRenderer.setColorMode(colorMode);
+        if (desiredRatio == 0 || desiredRatio > automaticRatio) {
+            desiredRatio = automaticRatio;
+        }
         if (desiredRatio != mDesiredHdrSdrRatio) {
             mDesiredHdrSdrRatio = desiredRatio;
             updateRenderHdrSdrRatio();
@@ -10543,6 +10547,8 @@
                 MergedConfiguration mergedConfiguration, InsetsState insetsState,
                 boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
                 boolean dragResizing) {
+            // Although this is a AIDL method, it will only be triggered in local process through
+            // either WindowStateResizeItem or WindowlessWindowManager.
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (viewAncestor != null) {
                 viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState,
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 2f04b0c..87537fbc 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -24,6 +24,8 @@
 
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
 import android.annotation.IdRes;
 import android.annotation.LayoutRes;
 import android.annotation.NonNull;
@@ -1330,6 +1332,47 @@
     }
 
     /**
+     * <p>Sets the desired about of HDR headroom to be used when rendering as a ratio of
+     * targetHdrPeakBrightnessInNits / targetSdrWhitePointInNits. Only applies when
+     * {@link #setColorMode(int)} is {@link ActivityInfo#COLOR_MODE_HDR}</p>
+     *
+     * <p>By default the system will choose an amount of HDR headroom that is appropriate
+     * for the underlying device capabilities & bit-depth of the panel. However, for some types
+     * of content this can end up being more headroom than necessary or desired. An example
+     * would be a messaging app or gallery thumbnail view where some amount of HDR pop is desired
+     * without overly influencing the perceived brightness of the majority SDR content. This can
+     * also be used to animate in/out of an HDR range for smoother transitions.</p>
+     *
+     * <p>Note: The actual amount of HDR headroom that will be given is subject to a variety
+     * of factors such as ambient conditions, display capabilities, or bit-depth limitations.
+     * See {@link Display#getHdrSdrRatio()} for more information as well as how to query the
+     * current value.</p>
+     *
+     * @param desiredHeadroom The amount of HDR headroom that is desired. Must be >= 1.0 (no HDR)
+     *                        and <= 10,000.0. Passing 0.0 will reset to the default, automatically
+     *                        chosen value.
+     * @see #getDesiredHdrHeadroom()
+     * @see Display#getHdrSdrRatio()
+     */
+    @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR)
+    public void setDesiredHdrHeadroom(
+            @FloatRange(from = 0.0f, to = 10000.0) float desiredHeadroom) {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        attrs.setDesiredHdrHeadroom(desiredHeadroom);
+        dispatchWindowAttributesChanged(attrs);
+    }
+
+    /**
+     * Get the desired amount of HDR headroom as set by {@link #setDesiredHdrHeadroom(float)}
+     * @return The amount of HDR headroom set, or 0 for automatic/default behavior.
+     * @see #setDesiredHdrHeadroom(float)
+     */
+    @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR)
+    public float getDesiredHdrHeadroom() {
+        return getAttributes().getDesiredHdrHeadroom();
+    }
+
+    /**
      * If {@code isPreferred} is true, this method requests that the connected display does minimal
      * post processing when this window is visible on the screen. Otherwise, it requests that the
      * display switches back to standard image processing.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 08f9c8c..a3b93b4 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -83,6 +83,7 @@
 import android.Manifest.permission;
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -1684,7 +1685,7 @@
      * orientation (e.g. with {@link android.app.Activity#setRequestedOrientation(int)}). This
      * listener gives application an opportunity to selectively react to device orientation changes.
      * The newly added listener will be called with current proposed rotation. Note that the context
-     * of this window manager instance must be a {@link android.annotation.UiContext}.
+     * of this window manager instance must be a {@code UiContext}.
      *
      * @param executor The executor on which callback method will be invoked.
      * @param listener Called when the proposed rotation for the context is being delivered.
@@ -1692,7 +1693,7 @@
      *                 {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180} and
      *                 {@link Surface#ROTATION_270}.
      * @throws UnsupportedOperationException if this method is called on an instance that is not
-     *         associated with a {@link android.annotation.UiContext}.
+     *         associated with a {@code UiContext}.
      */
     default void addProposedRotationListener(@NonNull @CallbackExecutor Executor executor,
             @NonNull IntConsumer listener) {
@@ -3114,7 +3115,7 @@
         /**
          * Never animate position changes of the window.
          *
-         * @see android.R.attr#Window_windowNoMoveAnimation
+         * @see android.R.styleable#Window_windowNoMoveAnimation
          * {@hide}
          */
         @UnsupportedAppUsage
@@ -4315,6 +4316,9 @@
         @ActivityInfo.ColorMode
         private int mColorMode = COLOR_MODE_DEFAULT;
 
+        /** @hide */
+        private float mDesiredHdrHeadroom = 0;
+
         /**
          * Carries the requests about {@link WindowInsetsController.Appearance} and
          * {@link WindowInsetsController.Behavior} to the system windows which can produce insets.
@@ -4527,7 +4531,7 @@
          * Set whether animations can be played for position changes on this window. If disabled,
          * the window will move to its new position instantly without animating.
          *
-         * @attr ref android.R.attr#Window_windowNoMoveAnimation
+         * @attr ref android.R.styleable#Window_windowNoMoveAnimation
          */
         public void setCanPlayMoveAnimation(boolean enable) {
             if (enable) {
@@ -4542,7 +4546,7 @@
          * This does not guarantee that an animation will be played in all such situations. For
          * example, drag-resizing may move the window but not play an animation.
          *
-         * @attr ref android.R.attr#Window_windowNoMoveAnimation
+         * @attr ref android.R.styleable#Window_windowNoMoveAnimation
          */
         public boolean canPlayMoveAnimation() {
             return (privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0;
@@ -4717,6 +4721,39 @@
         }
 
         /**
+         * <p>Sets the desired about of HDR headroom to be used when rendering as a ratio of
+         * targetHdrPeakBrightnessInNits / targetSdrWhitePointInNits. Only applies when
+         * {@link #setColorMode(int)} is {@link ActivityInfo#COLOR_MODE_HDR}</p>
+         *
+         * @see Window#setDesiredHdrHeadroom(float)
+         * @param desiredHeadroom Desired amount of HDR headroom. Must be in the range of 1.0 (SDR)
+         *                        to 10,000.0, or 0.0 to reset to default.
+         */
+        @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR)
+        public void setDesiredHdrHeadroom(
+                @FloatRange(from = 0.0f, to = 10000.0f) float desiredHeadroom) {
+            if (!Float.isFinite(desiredHeadroom)) {
+                throw new IllegalArgumentException("desiredHeadroom must be finite: "
+                        + desiredHeadroom);
+            }
+            if (desiredHeadroom != 0 && (desiredHeadroom < 1.0f || desiredHeadroom > 10000.0f)) {
+                throw new IllegalArgumentException(
+                        "desiredHeadroom must be 0.0 or in the range [1.0, 10000.0f], received: "
+                                + desiredHeadroom);
+            }
+            mDesiredHdrHeadroom = desiredHeadroom;
+        }
+
+        /**
+         * Get the desired amount of HDR headroom as set by {@link #setDesiredHdrHeadroom(float)}
+         * @return The amount of HDR headroom set, or 0 for automatic/default behavior.
+         */
+        @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR)
+        public float getDesiredHdrHeadroom() {
+            return mDesiredHdrHeadroom;
+        }
+
+        /**
          * <p>
          * Blurs the screen behind the window. The effect is similar to that of {@link #dimAmount},
          * but instead of dimmed, the content behind the window will be blurred (or combined with
@@ -4866,6 +4903,7 @@
             checkNonRecursiveParams();
             out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */);
             out.writeInt(mDisplayFlags);
+            out.writeFloat(mDesiredHdrHeadroom);
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR
@@ -4937,6 +4975,7 @@
             forciblyShownTypes = in.readInt();
             paramsForRotation = in.createTypedArray(LayoutParams.CREATOR);
             mDisplayFlags = in.readInt();
+            mDesiredHdrHeadroom = in.readFloat();
         }
 
         @SuppressWarnings({"PointlessBitwiseExpression"})
@@ -5197,6 +5236,11 @@
                 changes |= COLOR_MODE_CHANGED;
             }
 
+            if (mDesiredHdrHeadroom != o.mDesiredHdrHeadroom) {
+                mDesiredHdrHeadroom = o.mDesiredHdrHeadroom;
+                changes |= COLOR_MODE_CHANGED;
+            }
+
             if (preferMinimalPostProcessing != o.preferMinimalPostProcessing) {
                 preferMinimalPostProcessing = o.preferMinimalPostProcessing;
                 changes |= MINIMAL_POST_PROCESSING_PREFERENCE_CHANGED;
@@ -5424,6 +5468,9 @@
             if (mColorMode != COLOR_MODE_DEFAULT) {
                 sb.append(" colorMode=").append(ActivityInfo.colorModeToString(mColorMode));
             }
+            if (mDesiredHdrHeadroom != 0) {
+                sb.append(" desiredHdrHeadroom=").append(mDesiredHdrHeadroom);
+            }
             if (preferMinimalPostProcessing) {
                 sb.append(" preferMinimalPostProcessing=");
                 sb.append(preferMinimalPostProcessing);
diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java
index 7ad43c7..26298bc 100644
--- a/core/java/android/view/WindowMetrics.java
+++ b/core/java/android/view/WindowMetrics.java
@@ -47,7 +47,6 @@
  * @see WindowInsets#getInsets(int)
  * @see WindowManager#getCurrentWindowMetrics()
  * @see WindowManager#getMaximumWindowMetrics()
- * @see android.annotation.UiContext
  */
 public final class WindowMetrics {
     @NonNull
@@ -99,8 +98,7 @@
     }
 
     /**
-     * Returns the bounds of the area associated with this window or
-     * {@link android.annotation.UiContext}.
+     * Returns the bounds of the area associated with this window or {@code UiContext}.
      * <p>
      * <b>Note that the size of the reported bounds can have different size than
      * {@link Display#getSize(Point)}.</b> This method reports the window size including all system
@@ -133,7 +131,7 @@
 
     /**
      * Returns the {@link WindowInsets} of the area associated with this window or
-     * {@link android.annotation.UiContext}.
+     * {@code UiContext}.
      *
      * @return the {@link WindowInsets} of the visual area.
      */
@@ -146,9 +144,8 @@
     }
 
     /**
-     * Returns the density of the area associated with this window or
-     * {@link android.annotation.UiContext}, which uses the same units as
-     * {@link android.util.DisplayMetrics#density}.
+     * Returns the density of the area associated with this window or {@code UiContext},
+     * which uses the same units as {@link android.util.DisplayMetrics#density}.
      *
      * @see android.util.DisplayMetrics#DENSITY_DEFAULT
      * @see android.util.DisplayMetrics#density
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index e6a8b78..43bfe13 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -1562,9 +1562,8 @@
      * describes the action.
      * </p>
      * <p>
-     * Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View,
-     * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the
-     * view.
+     * Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View, CharSequence,
+     * AccessibilityViewCommand)} to register an action directly on the view.
      * <p>
      *   <strong>Note:</strong> Cannot be called from an
      *   {@link android.accessibilityservice.AccessibilityService}.
@@ -5167,8 +5166,7 @@
      * </p>
      * <aside class="note">
      * <b>Note:</b> Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View,
-     * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the
-     * view.
+     * CharSequence, AccessibilityViewCommand)} to register an action directly on the view.
      * </p>
      */
     public static final class AccessibilityAction implements Parcelable {
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index a76780d..32256b9 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -56,7 +56,7 @@
     private static final int SEQUENTIALLY = 1;
 
      /**
-     * For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above,
+     * For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and above,
      * this change ID enables to use expectedPresentationTime instead of the frameTime
      * for the frame start time .
      *
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 2c7d326..42b3e38 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -60,7 +60,9 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -377,6 +379,30 @@
     public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE =
             "content_protection_buffer_size";
 
+    /**
+     * Sets the config for content protection required groups.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG =
+            "content_protection_required_groups_config";
+
+    /**
+     * Sets the config for content protection optional groups.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG =
+            "content_protection_optional_groups_config";
+
+    /**
+     * Sets the threshold for content protection optional groups.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD =
+            "content_protection_optional_groups_threshold";
+
     /** @hide */
     @TestApi
     public static final int LOGGING_LEVEL_OFF = 0;
@@ -417,6 +443,18 @@
     public static final int DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE = 5000;
     /** @hide */
     public static final int DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE = 150;
+    /** @hide */
+    public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS =
+            Collections.emptyList();
+    /** @hide */
+    public static final String DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG = "";
+    /** @hide */
+    public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS =
+            Collections.emptyList();
+    /** @hide */
+    public static final String DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG = "";
+    /** @hide */
+    public static final int DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD = 0;
 
     private final Object mLock = new Object();
 
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index dc3d323..bb815c0 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -183,7 +183,8 @@
     public static final int FLUSH_REASON_VIEW_TREE_APPEARED = 10;
 
     /**
-     * After {@link UPSIDE_DOWN_CAKE}, {@link #notifyViewsDisappeared(AutofillId, long[])} wraps
+     * After {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * {@link #notifyViewsDisappeared(AutofillId, long[])} wraps
      * the virtual children with a pair of view tree appearing and view tree appeared events.
      */
     @ChangeId
diff --git a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
index 7e06f87..5c86feb 100644
--- a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
+++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
@@ -6,3 +6,10 @@
     description: "If true, content protection blocklist is mutable and can be updated."
     bug: "301658008"
 }
+
+flag {
+    name: "parse_groups_config_enabled"
+    namespace: "content_protection"
+    description: "If true, content protection groups config will be parsed."
+    bug: "302187922"
+}
diff --git a/core/java/android/view/displayhash/DisplayHashResultCallback.java b/core/java/android/view/displayhash/DisplayHashResultCallback.java
index 6e3d9a8..927874f 100644
--- a/core/java/android/view/displayhash/DisplayHashResultCallback.java
+++ b/core/java/android/view/displayhash/DisplayHashResultCallback.java
@@ -106,7 +106,7 @@
      * {@link android.view.View#generateDisplayHash(String, Rect, Executor,
      * DisplayHashResultCallback)} results in an error and cannot generate a display hash.
      *
-     * @param errorCode One of the values in {@link DisplayHashErrorCode}
+     * @param errorCode the error code
      */
     void onDisplayHashError(@DisplayHashErrorCode int errorCode);
 }
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index a92420a..c5114b9 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -47,7 +47,6 @@
 import android.view.MotionEvent.ToolType;
 import android.view.View;
 import android.view.autofill.AutofillId;
-import android.widget.Editor;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.InputMethodDebug;
@@ -722,9 +721,9 @@
     private boolean mIsStylusHandwritingEnabled;
 
     /**
-     * Set {@code true} if the {@link Editor} has
+     * Set {@code true} if the {@code Editor} has
      * {@link InputMethodManager#startStylusHandwriting stylus handwriting} enabled.
-     * {@code false} by default, {@link Editor} must set it {@code true} to indicate that
+     * {@code false} by default, {@code Editor} must set it {@code true} to indicate that
      * it supports stylus handwriting.
      *
      * @param enabled {@code true} if stylus handwriting is enabled.
@@ -736,7 +735,7 @@
     }
 
     /**
-     * Returns {@code true} when an {@link Editor} has stylus handwriting enabled.
+     * Returns {@code true} when an {@code Editor} has stylus handwriting enabled.
      * {@code false} by default.
      * @see #setStylusHandwritingEnabled(boolean)
      * @see InputMethodManager#isStylusHandwritingAvailable()
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 5bb1e93..8159af3 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -100,7 +100,6 @@
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
-import android.widget.Editor;
 import android.window.ImeOnBackInvokedDispatcher;
 import android.window.WindowOnBackInvokedDispatcher;
 
@@ -2374,16 +2373,16 @@
      * Prepares delegation of starting stylus handwriting session to a different editor in same
      * or different window than the view on which initial handwriting stroke was detected.
      *
-     * Delegation can be used to start stylus handwriting session before the {@link Editor} view or
+     * Delegation can be used to start stylus handwriting session before the {@code Editor} view or
      * its {@link InputConnection} is started. Calling this method starts buffering of stylus
      * motion events until {@link #acceptStylusHandwritingDelegation(View)} is called, at which
      * point the handwriting session can be started and the buffered stylus motion events will be
      * delivered to the IME.
      * e.g. Delegation can be used when initial handwriting stroke is
-     * on a pseudo {@link Editor} like widget (with no {@link InputConnection}) but actual
-     * {@link Editor} is on a different window.
+     * on a pseudo {@code Editor} like widget (with no {@link InputConnection}) but actual
+     * {@code Editor} is on a different window.
      *
-     * <p> Note: If an actual {@link Editor} capable of {@link InputConnection} is being scribbled
+     * <p> Note: If an actual {@code Editor} capable of {@link InputConnection} is being scribbled
      * upon using stylus, use {@link #startStylusHandwriting(View)} instead.</p>
      *
      * @param delegatorView the view that receives initial stylus stroke and delegates it to the
@@ -2402,21 +2401,21 @@
      * different window in a different package than the view on which initial handwriting stroke
      * was detected.
      *
-     * Delegation can be used to start stylus handwriting session before the {@link Editor} view or
+     * Delegation can be used to start stylus handwriting session before the {@code Editor} view or
      * its {@link InputConnection} is started. Calling this method starts buffering of stylus
      * motion events until {@link #acceptStylusHandwritingDelegation(View, String)} is called, at
      * which point the handwriting session can be started and the buffered stylus motion events will
      * be delivered to the IME.
      * e.g. Delegation can be used when initial handwriting stroke is
-     * on a pseudo {@link Editor} like widget (with no {@link InputConnection}) but actual
-     * {@link Editor} is on a different window in the given package.
+     * on a pseudo {@code Editor} like widget (with no {@link InputConnection}) but actual
+     * {@code Editor} is on a different window in the given package.
      *
      * <p>Note: If delegator and delegate are in same package use
      * {@link #prepareStylusHandwritingDelegation(View)} instead.</p>
      *
      * @param delegatorView  the view that receives initial stylus stroke and delegates it to the
      * actual editor. Its window must {@link View#hasWindowFocus have focus}.
-     * @param delegatePackageName package name that contains actual {@link Editor} which should
+     * @param delegatePackageName package name that contains actual {@code Editor} which should
      *  start stylus handwriting session by calling {@link #acceptStylusHandwritingDelegation}.
      * @see #prepareStylusHandwritingDelegation(View)
      * @see #acceptStylusHandwritingDelegation(View, String)
diff --git a/core/java/android/view/inspector/PropertyReader.java b/core/java/android/view/inspector/PropertyReader.java
index 5be0e3f..78de7e1 100644
--- a/core/java/android/view/inspector/PropertyReader.java
+++ b/core/java/android/view/inspector/PropertyReader.java
@@ -124,7 +124,7 @@
     void readObject(int id, @Nullable Object value);
 
     /**
-     * Read a color packed into a {@link ColorInt} as a property.
+     * Read a color packed into an int as a property.
      *
      * @param id Identifier of the property from a {@link PropertyMapper}
      * @param value Value of the property
diff --git a/core/java/android/window/SystemPerformanceHinter.java b/core/java/android/window/SystemPerformanceHinter.java
index 07ac292..2736b68 100644
--- a/core/java/android/window/SystemPerformanceHinter.java
+++ b/core/java/android/window/SystemPerformanceHinter.java
@@ -211,7 +211,11 @@
                     session.displayId);
             mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
                     FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
-            mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH);
+            // smoothSwitchOnly is false to request a higher framerate, even if it means switching
+            // the display mode will cause would jank on non-VRR devices because keeping a lower
+            // refresh rate would mean a poorer user experience.
+            mTransaction.setFrameRateCategory(
+                    displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH, /* smoothSwitchOnly= */ false);
             transactionChanged = true;
             Trace.beginAsyncSection("PerfHint-framerate-" + session.displayId + "-"
                     + session.reason, session.traceCookie);
@@ -251,7 +255,11 @@
                     session.displayId);
             mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
                     FRAME_RATE_SELECTION_STRATEGY_SELF);
-            mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT);
+            // smoothSwitchOnly is false to request a higher framerate, even if it means switching
+            // the display mode will cause would jank on non-VRR devices because keeping a lower
+            // refresh rate would mean a poorer user experience.
+            mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT,
+                    /* smoothSwitchOnly= */ false);
             transactionChanged = true;
             Trace.endAsyncSection("PerfHint-framerate-" + session.displayId + "-" + session.reason,
                     session.traceCookie);
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index d2a16a3..61f340a 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -29,6 +29,7 @@
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
@@ -361,6 +362,15 @@
     }
 
     /**
+     * Whether this transition contains any changes to the window hierarchy,
+     * including keyguard visibility.
+     */
+    public boolean hasChangesOrSideEffects() {
+        return !mChanges.isEmpty() || isKeyguardGoingAway()
+                || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0;
+    }
+
+    /**
      * Whether this transition includes keyguard going away.
      */
     public boolean isKeyguardGoingAway() {
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index ec5d4ff..24dc6db 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -22,3 +22,10 @@
     description: "Whether the TaskFragment system organizer feature is enabled"
     bug: "284050041"
 }
+
+flag {
+    namespace: "windowing_sdk"
+    name: "window_state_resize_item_flag"
+    description: "Whether to dispatch window resize through ClientTransaction is enabled"
+    bug: "301870955"
+}
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 58376a7..0801dd8 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -62,16 +62,14 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.attribute.BasicFileAttributes;
+ import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Deque;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
+import java.util.Queue;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.function.Predicate;
-import java.util.regex.Pattern;
 
 /**
  * A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local
@@ -89,6 +87,8 @@
             DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER,
             DocumentsContract.QUERY_ARG_MIME_TYPES);
 
+    private static final int MAX_RESULTS_NUMBER = 23;
+
     private static String joinNewline(String... args) {
         return TextUtils.join("\n", args);
     }
@@ -375,62 +375,53 @@
     }
 
     /**
-     * This method is similar to
-     * {@link DocumentsProvider#queryChildDocuments(String, String[], String)}. This method returns
-     * all children documents including hidden directories/files.
-     *
-     * <p>
-     * In a scoped storage world, access to "Android/data" style directories are hidden for privacy
-     * reasons. This method may show privacy sensitive data, so its usage should only be in
-     * restricted modes.
-     *
-     * @param parentDocumentId the directory to return children for.
-     * @param projection list of {@link Document} columns to put into the
-     *            cursor. If {@code null} all supported columns should be
-     *            included.
-     * @param sortOrder how to order the rows, formatted as an SQL
-     *            {@code ORDER BY} clause (excluding the ORDER BY itself).
-     *            Passing {@code null} will use the default sort order, which
-     *            may be unordered. This ordering is a hint that can be used to
-     *            prioritize how data is fetched from the network, but UI may
-     *            always enforce a specific ordering
-     * @throws FileNotFoundException when parent document doesn't exist or query fails
+     * WARNING: this method should really be {@code final}, but for the backward compatibility it's
+     * not; new classes that extend {@link FileSystemProvider} should override
+     * {@link #queryChildDocuments(String, String[], String, boolean)}, not this method.
      */
-    protected Cursor queryChildDocumentsShowAll(
-            String parentDocumentId, String[] projection, String sortOrder)
-            throws FileNotFoundException {
-        return queryChildDocuments(parentDocumentId, projection, sortOrder, File -> true);
-    }
-
     @Override
-    public Cursor queryChildDocuments(
-            String parentDocumentId, String[] projection, String sortOrder)
+    public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder)
             throws FileNotFoundException {
-        // Access to some directories is hidden for privacy reasons.
-        return queryChildDocuments(parentDocumentId, projection, sortOrder, this::shouldShow);
+        return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ false);
     }
 
-    private Cursor queryChildDocuments(
-            String parentDocumentId, String[] projection, String sortOrder,
-            @NonNull Predicate<File> filter) throws FileNotFoundException {
-        final File parent = getFileForDocId(parentDocumentId);
-        final MatrixCursor result = new DirectoryCursor(
-                resolveProjection(projection), parentDocumentId, parent);
+    /**
+     * This method is similar to {@link #queryChildDocuments(String, String[], String)}, however, it
+     * could return <b>all</b> content of the directory, <b>including restricted (hidden)
+     * directories and files</b>.
+     * <p>
+     * In the scoped storage world, some directories and files (e.g. {@code Android/data/} and
+     * {@code Android/obb/} on the external storage) are hidden for privacy reasons.
+     * Hence, this method may reveal privacy-sensitive data, thus should be used with extra care.
+     */
+    @Override
+    public final Cursor queryChildDocumentsForManage(String documentId, String[] projection,
+            String sortOrder) throws FileNotFoundException {
+        return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ true);
+    }
 
-        if (!filter.test(parent)) {
-            Log.w(TAG, "No permission to access parentDocumentId: " + parentDocumentId);
+    protected Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder,
+            boolean includeHidden) throws FileNotFoundException {
+        final File parent = getFileForDocId(documentId);
+        final MatrixCursor result = new DirectoryCursor(
+                resolveProjection(projection), documentId, parent);
+
+        if (!parent.isDirectory()) {
+            Log.w(TAG, '"' + documentId + "\" is not a directory");
             return result;
         }
 
-        if (parent.isDirectory()) {
-            for (File file : FileUtils.listFilesOrEmpty(parent)) {
-                if (filter.test(file)) {
-                    includeFile(result, null, file);
-                }
-            }
-        } else {
-            Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory");
+        if (!includeHidden && shouldHideDocument(documentId)) {
+            Log.w(TAG, "Queried directory \"" + documentId + "\" is hidden");
+            return result;
         }
+
+        for (File file : FileUtils.listFilesOrEmpty(parent)) {
+            if (!includeHidden && shouldHideDocument(file)) continue;
+
+            includeFile(result, null, file);
+        }
+
         return result;
     }
 
@@ -452,23 +443,29 @@
      *
      * @see ContentResolver#EXTRA_HONORED_ARGS
      */
-    protected final Cursor querySearchDocuments(
-            File folder, String[] projection, Set<String> exclusion, Bundle queryArgs)
-            throws FileNotFoundException {
+    protected final Cursor querySearchDocuments(File folder, String[] projection,
+            Set<String> exclusion, Bundle queryArgs) throws FileNotFoundException {
         final MatrixCursor result = new MatrixCursor(resolveProjection(projection));
-        final List<File> pending = new ArrayList<>();
-        pending.add(folder);
-        while (!pending.isEmpty() && result.getCount() < 24) {
-            final File file = pending.remove(0);
-            if (shouldHide(file)) continue;
+
+        // We'll be a running a BFS here.
+        final Queue<File> pending = new ArrayDeque<>();
+        pending.offer(folder);
+
+        while (!pending.isEmpty() && result.getCount() < MAX_RESULTS_NUMBER) {
+            final File file = pending.poll();
+
+            // Skip hidden documents (both files and directories)
+            if (shouldHideDocument(file)) continue;
 
             if (file.isDirectory()) {
                 for (File child : FileUtils.listFilesOrEmpty(file)) {
-                    pending.add(child);
+                    pending.offer(child);
                 }
             }
-            if (!exclusion.contains(file.getAbsolutePath()) && matchSearchQueryArguments(file,
-                    queryArgs)) {
+
+            if (exclusion.contains(file.getAbsolutePath())) continue;
+
+            if (matchSearchQueryArguments(file, queryArgs)) {
                 includeFile(result, null, file);
             }
         }
@@ -612,26 +609,23 @@
 
         final int flagIndex = ArrayUtils.indexOf(columns, Document.COLUMN_FLAGS);
         if (flagIndex != -1) {
+            final boolean isDir = mimeType.equals(Document.MIME_TYPE_DIR);
             int flags = 0;
             if (file.canWrite()) {
-                if (mimeType.equals(Document.MIME_TYPE_DIR)) {
+                flags |= Document.FLAG_SUPPORTS_DELETE;
+                flags |= Document.FLAG_SUPPORTS_RENAME;
+                flags |= Document.FLAG_SUPPORTS_MOVE;
+                if (isDir) {
                     flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
-                    flags |= Document.FLAG_SUPPORTS_DELETE;
-                    flags |= Document.FLAG_SUPPORTS_RENAME;
-                    flags |= Document.FLAG_SUPPORTS_MOVE;
-
-                    if (shouldBlockFromTree(docId)) {
-                        flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE;
-                    }
-
                 } else {
                     flags |= Document.FLAG_SUPPORTS_WRITE;
-                    flags |= Document.FLAG_SUPPORTS_DELETE;
-                    flags |= Document.FLAG_SUPPORTS_RENAME;
-                    flags |= Document.FLAG_SUPPORTS_MOVE;
                 }
             }
 
+            if (isDir && shouldBlockDirectoryFromTree(docId)) {
+                flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE;
+            }
+
             if (mimeType.startsWith("image/")) {
                 flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
             }
@@ -664,22 +658,36 @@
         return row;
     }
 
-    private static final Pattern PATTERN_HIDDEN_PATH = Pattern.compile(
-            "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb|sandbox)$");
+    /**
+     * Some providers may want to restrict access to certain directories and files,
+     * e.g. <i>"Android/data"</i> and <i>"Android/obb"</i> on the shared storage for
+     * privacy reasons.
+     * Such providers should override this method.
+     */
+    protected boolean shouldHideDocument(@NonNull String documentId)
+            throws FileNotFoundException {
+        return false;
+    }
 
     /**
-     * In a scoped storage world, access to "Android/data" style directories are
-     * hidden for privacy reasons.
+     * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of
+     * a {@link String} {@code documentId}.
+     *
+     * @see #shouldHideDocument(String)
      */
-    protected boolean shouldHide(@NonNull File file) {
-        return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches());
+    protected final boolean shouldHideDocument(@NonNull File document)
+            throws FileNotFoundException {
+        return shouldHideDocument(getDocIdForFile(document));
     }
 
-    private boolean shouldShow(@NonNull File file) {
-        return !shouldHide(file);
-    }
-
-    protected boolean shouldBlockFromTree(@NonNull String docId) {
+    /**
+     * @return if the directory that should be blocked from being selected when the user launches
+     * an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} intent.
+     *
+     * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE
+     */
+    protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId)
+            throws FileNotFoundException {
         return false;
     }
 
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b5d70d3..50253cf 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -1315,7 +1315,16 @@
         ALOGI("VM exiting with result code %d.", code);
         onExit(code);
     }
+
+#ifdef __ANDROID_CLANG_COVERAGE__
+    // When compiled with coverage, a function is registered with atexit to call
+    // `__llvm_profile_write_file` when the process exit.
+    // For Clang code coverage to work, call exit instead of _exit to run hooks
+    // registered with atexit.
+    ::exit(code);
+#else
     ::_exit(code);
+#endif
 }
 
 void AndroidRuntime::onVmCreated(JNIEnv* env)
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index ba644eb..178c0d0 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -963,10 +963,11 @@
 }
 
 static void nativeSetFrameRateCategory(JNIEnv* env, jclass clazz, jlong transactionObj,
-                                       jlong nativeObject, jint category) {
+                                       jlong nativeObject, jint category,
+                                       jboolean smoothSwitchOnly) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
     const auto ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
-    transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category));
+    transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category), smoothSwitchOnly);
 }
 
 static void nativeSetFrameRateSelectionStrategy(JNIEnv* env, jclass clazz, jlong transactionObj,
@@ -2181,7 +2182,7 @@
             (void*)nativeSetFrameRate },
     {"nativeSetDefaultFrameRateCompatibility", "(JJI)V",
             (void*)nativeSetDefaultFrameRateCompatibility},
-    {"nativeSetFrameRateCategory", "(JJI)V",
+    {"nativeSetFrameRateCategory", "(JJIZ)V",
             (void*)nativeSetFrameRateCategory},
     {"nativeSetFrameRateSelectionStrategy", "(JJI)V",
             (void*)nativeSetFrameRateSelectionStrategy},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b0ecc60..a54a563 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2108,12 +2108,12 @@
         android:protectionLevel="signature" />
 
     <!-- Allows direct access to the <RemoteAuth>Service interfaces.
-         @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) -->
+         @hide -->
     <permission android:name="android.permission.MANAGE_REMOTE_AUTH"
                 android:protectionLevel="signature" />
 
     <!-- Allows direct access to the <RemoteAuth>Service authentication methods.
-         @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) -->
+         @hide -->
     <permission android:name="android.permission.USE_REMOTE_AUTH"
                 android:protectionLevel="signature" />
 
@@ -7235,13 +7235,23 @@
 
     <!-- @SystemApi Required for the privileged assistant apps targeting
          {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
-         that receive voice trigger from the trusted hotword detection service.
+         that receive voice trigger from a trusted hotword detection service.
          <p>Protection level: signature|privileged|appop
          @FlaggedApi("android.permission.flags.voice_activation_permission_apis")
          @hide -->
     <permission android:name="android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO"
                 android:protectionLevel="signature|privileged|appop" />
 
+    <!-- @SystemApi Required for the privileged assistant apps targeting
+         {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
+         that receive training data from the sandboxed hotword detection service or visual
+         query detection service.
+         <p>Protection level: internal|appop
+         @FlaggedApi("android.permission.flags.voice_activation_permission_apis")
+         @hide -->
+    <permission android:name="android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA"
+                android:protectionLevel="internal|appop" />
+
     <!-- @SystemApi Allows requesting the framework broadcast the
          {@link Intent#ACTION_DEVICE_CUSTOMIZATION_READY} intent.
          @hide -->
diff --git a/core/res/res/drawable-nodpi/usb_cable_unknown_issue.xml b/core/res/res/drawable-nodpi/usb_cable_unknown_issue.xml
new file mode 100644
index 0000000..dddad81
--- /dev/null
+++ b/core/res/res/drawable-nodpi/usb_cable_unknown_issue.xml
@@ -0,0 +1,27 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="20"
+        android:viewportHeight="20">
+    <path
+        android:pathData="M15.333,5.333V4.667C15.333,4.3 15.033,4 14.667,4L13.333,4C12.967,4 12.667,4.3 12.667,4.667V5.333H12V8C12,8.367 12.3,8.667 12.667,8.667H13.333L13.333,13.333C13.333,14.067 12.733,14.667 12,14.667C11.267,14.667 10.667,14.067 10.667,13.333L10.667,11.333V6.667C10.667,5.193 9.473,4 8,4C6.527,4 5.333,5.193 5.333,6.667L5.333,11.333H4.667C4.3,11.333 4,11.633 4,12L4,14.667H4.667V15.333C4.667,15.7 4.967,16 5.333,16H6.667C7.033,16 7.333,15.7 7.333,15.333V14.667H8L8,12C8,11.633 7.7,11.333 7.333,11.333H6.667L6.667,6.667C6.667,5.933 7.267,5.333 8,5.333C8.733,5.333 9.333,5.933 9.333,6.667V11.333L9.333,13.333C9.333,14.807 10.527,16 12,16C13.473,16 14.667,14.807 14.667,13.333L14.667,8.667H15.333C15.7,8.667 16,8.367 16,8V5.333H15.333Z"
+        android:fillColor="#FFFFFFFF"
+        android:fillType="evenOdd"/>
+</vector>
+
+
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e54347f..04fd70a 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -10118,6 +10118,9 @@
              screen that can be used to provide more information about a provider. For
              longer strings it will be truncated. -->
         <attr name="settingsSubtitle" format="string" />
+        <!-- Fully qualified class name of an activity that allows the user to modify
+             the settings for this service. -->
+        <attr name="settingsActivity" />
     </declare-styleable>
 
     <!-- A list of capabilities that indicates to the OS what kinds of credentials
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index fac6aac..41356bd 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -826,6 +826,9 @@
          security policy. [CHAR_LIMIT=NONE]-->
     <string name="notification_channel_accessibility_security_policy">Accessibility usage</string>
 
+    <!-- Text shown when viewing channel settings for notifications related to displays -->
+    <string name="notification_channel_display">Display</string>
+
     <!-- Label for foreground service notification when one app is running.
     [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=6826789589341671842] -->
     <string name="foreground_service_app_in_background"><xliff:g id="app_name">%1$s</xliff:g> is
@@ -6290,6 +6293,11 @@
     <!-- Toast message that is shown when the user mutes the microphone from the keyboard. [CHAR LIMIT=TOAST] -->
     <string name="mic_access_off_toast">Microphone is blocked</string>
 
+    <!-- Title of connected display unavailable notifications. [CHAR LIMIT=NONE] -->
+    <string name="connected_display_unavailable_notification_title">Can\'t mirror to display</string>
+    <!-- Content of connected display unavailable notification. [CHAR LIMIT=NONE] -->
+    <string name="connected_display_unavailable_notification_content">Use a different cable and try again</string>
+
     <!-- Name of concurrent display notifications. [CHAR LIMIT=NONE] -->
     <string name="concurrent_display_notification_name">Dual screen</string>
     <!-- Title of concurrent display active notification. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 49a5a72..efe5c3b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1997,6 +1997,7 @@
   <java-symbol type="drawable" name="stat_sys_throttled" />
   <java-symbol type="drawable" name="vpn_connected" />
   <java-symbol type="drawable" name="vpn_disconnected" />
+  <java-symbol type="drawable" name="usb_cable_unknown_issue" />
   <java-symbol type="id" name="ask_checkbox" />
   <java-symbol type="id" name="compat_checkbox" />
   <java-symbol type="id" name="original_app_icon" />
@@ -3813,6 +3814,7 @@
   <java-symbol type="string" name="notification_channel_do_not_disturb" />
   <java-symbol type="string" name="notification_channel_accessibility_magnification" />
   <java-symbol type="string" name="notification_channel_accessibility_security_policy" />
+  <java-symbol type="string" name="notification_channel_display" />
   <java-symbol type="string" name="config_defaultAutofillService" />
   <java-symbol type="string" name="config_defaultFieldClassificationService" />
   <java-symbol type="string" name="config_defaultOnDeviceSpeechRecognitionService" />
@@ -5066,6 +5068,8 @@
   <java-symbol type="array" name="device_state_notification_thermal_contents"/>
   <java-symbol type="array" name="device_state_notification_power_save_titles"/>
   <java-symbol type="array" name="device_state_notification_power_save_contents"/>
+  <java-symbol type="string" name="connected_display_unavailable_notification_title"/>
+  <java-symbol type="string" name="connected_display_unavailable_notification_content"/>
   <java-symbol type="string" name="concurrent_display_notification_name"/>
   <java-symbol type="string" name="concurrent_display_notification_active_title"/>
   <java-symbol type="string" name="concurrent_display_notification_active_content"/>
diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java
new file mode 100644
index 0000000..c00eb91
--- /dev/null
+++ b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static org.mockito.Mockito.verify;
+
+import android.app.ClientTransactionHandler;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.util.MergedConfiguration;
+import android.view.IWindow;
+import android.view.InsetsState;
+import android.window.ClientWindowFrames;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link WindowStateResizeItem}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksCoreTests:WindowStateResizeItemTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class WindowStateResizeItemTest {
+
+    @Mock
+    private ClientTransactionHandler mHandler;
+    @Mock
+    private PendingTransactionActions mPendingActions;
+    @Mock
+    private IWindow mWindow;
+    @Mock
+    private ClientWindowFrames mFrames;
+    @Mock
+    private MergedConfiguration mConfiguration;
+    @Mock
+    private InsetsState mInsetsState;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testExecute() throws RemoteException {
+        final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames,
+                true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */,
+                true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
+                true /* dragResizing */);
+        item.execute(mHandler, mPendingActions);
+
+        verify(mWindow).resized(mFrames,
+                true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */,
+                true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
+                true /* dragResizing */);
+    }
+}
diff --git a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java
index f8348d2..eefa6e4 100644
--- a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java
+++ b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java
@@ -32,6 +32,8 @@
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.util.List;
+
 /**
  * Unit test for {@link ContentCaptureOptions}.
  *
@@ -44,6 +46,9 @@
     private static final ComponentName CONTEXT_COMPONENT = new ComponentName("marco", "polo");
     private static final ComponentName COMPONENT1 = new ComponentName("comp", "one");
     private static final ComponentName COMPONENT2 = new ComponentName("two", "comp");
+    private static final List<List<String>> CONTENT_PROTECTION_REQUIRED_GROUPS =
+            List.of(List.of("first"), List.of("second", "third"), List.of());
+    private static final List<List<String>> CONTENT_PROTECTION_OPTIONAL_GROUPS = List.of();
     private static final ContentCaptureOptions CONTENT_CAPTURE_OPTIONS =
             new ContentCaptureOptions(
                     /* loggingLevel= */ 1000,
@@ -55,7 +60,10 @@
                     /* enableReceiver= */ false,
                     new ContentCaptureOptions.ContentProtectionOptions(
                             /* enableReceiver= */ true,
-                            /* bufferSize= */ 2001),
+                            /* bufferSize= */ 2001,
+                            CONTENT_PROTECTION_REQUIRED_GROUPS,
+                            CONTENT_PROTECTION_OPTIONAL_GROUPS,
+                            /* optionalGroupsThreshold= */ 2002),
                     /* whitelistedComponents= */ toSet(COMPONENT1, COMPONENT2));
 
     @Mock private Context mContext;
@@ -134,6 +142,19 @@
                         .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver)
                         .append(", bufferSize=")
                         .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize)
+                        .append(", requiredGroupsSize=")
+                        .append(
+                                CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.requiredGroups
+                                        .size())
+                        .append(", optionalGroupsSize=")
+                        .append(
+                                CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.optionalGroups
+                                        .size())
+                        .append(", optionalGroupsThreshold=")
+                        .append(
+                                CONTENT_CAPTURE_OPTIONS
+                                        .contentProtectionOptions
+                                        .optionalGroupsThreshold)
                         .append("], whitelisted=")
                         .append(CONTENT_CAPTURE_OPTIONS.whitelistedComponents)
                         .append(']')
@@ -166,6 +187,15 @@
                 .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver);
         assertThat(actual.contentProtectionOptions.bufferSize)
                 .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize);
+        assertThat(actual.contentProtectionOptions.requiredGroups)
+                .containsExactlyElementsIn(
+                        CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.requiredGroups);
+        assertThat(actual.contentProtectionOptions.optionalGroups)
+                .containsExactlyElementsIn(
+                        CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.optionalGroups);
+        assertThat(actual.contentProtectionOptions.optionalGroupsThreshold)
+                .isEqualTo(
+                        CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.optionalGroupsThreshold);
         assertThat(actual.whitelistedComponents)
                 .containsExactlyElementsIn(CONTENT_CAPTURE_OPTIONS.whitelistedComponents);
     }
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index 0941a2b..6c14ee3 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -137,7 +137,7 @@
             );
         });
 
-        PollingCheck.waitFor(/* timeout= */ 7000, () -> {
+        PollingCheck.waitFor(/* timeout= */ 10000, () -> {
             AtomicBoolean isActivityAtCorrectScale = new AtomicBoolean(false);
             rule.getScenario().onActivity(activity ->
                     isActivityAtCorrectScale.set(
@@ -163,7 +163,7 @@
         });
 
         PollingCheck.waitFor(
-                /* timeout= */ 5000,
+                /* timeout= */ 10000,
                 () -> InstrumentationRegistry.getInstrumentation()
                                         .getContext()
                                         .getResources()
diff --git a/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java b/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java
deleted file mode 100644
index e1f9523..0000000
--- a/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java
+++ /dev/null
@@ -1,126 +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.os.health;
-
-import static androidx.test.InstrumentationRegistry.getContext;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.os.ConditionVariable;
-import android.os.PowerMonitor;
-import android.os.PowerMonitorReadings;
-
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class SystemHealthManagerTest {
-    private List<PowerMonitor> mPowerMonitorInfo;
-    private PowerMonitorReadings mReadings;
-    private RuntimeException mException;
-
-    @Test
-    public void getPowerMonitors() {
-        SystemHealthManager shm = getContext().getSystemService(SystemHealthManager.class);
-        List<PowerMonitor> powerMonitorInfo = shm.getSupportedPowerMonitors();
-        assertThat(powerMonitorInfo).isNotNull();
-        if (powerMonitorInfo.isEmpty()) {
-            // This device does not support PowerStats HAL
-            return;
-        }
-
-        PowerMonitor consumerMonitor = null;
-        PowerMonitor measurementMonitor = null;
-        for (PowerMonitor pmi : powerMonitorInfo) {
-            if (pmi.type == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
-                measurementMonitor = pmi;
-            } else {
-                consumerMonitor = pmi;
-            }
-        }
-
-        List<PowerMonitor> selectedMonitors = new ArrayList<>();
-        if (consumerMonitor != null) {
-            selectedMonitors.add(consumerMonitor);
-        }
-        if (measurementMonitor != null) {
-            selectedMonitors.add(measurementMonitor);
-        }
-
-        PowerMonitorReadings readings = shm.getPowerMonitorReadings(selectedMonitors);
-
-        for (PowerMonitor monitor : selectedMonitors) {
-            assertThat(readings.getConsumedEnergyUws(monitor)).isAtLeast(0);
-            assertThat(readings.getTimestamp(monitor)).isGreaterThan(0);
-        }
-    }
-
-    @Test
-    public void getPowerMonitorsAsync() {
-        SystemHealthManager shm = getContext().getSystemService(SystemHealthManager.class);
-        ConditionVariable done = new ConditionVariable();
-        shm.getSupportedPowerMonitors(null, pms -> {
-            mPowerMonitorInfo = pms;
-            done.open();
-        });
-        done.block();
-        assertThat(mPowerMonitorInfo).isNotNull();
-        if (mPowerMonitorInfo.isEmpty()) {
-            // This device does not support PowerStats HAL
-            return;
-        }
-
-        PowerMonitor consumerMonitor = null;
-        PowerMonitor measurementMonitor = null;
-        for (PowerMonitor pmi : mPowerMonitorInfo) {
-            if (pmi.type == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
-                measurementMonitor = pmi;
-            } else {
-                consumerMonitor = pmi;
-            }
-        }
-
-        List<PowerMonitor> selectedMonitors = new ArrayList<>();
-        if (consumerMonitor != null) {
-            selectedMonitors.add(consumerMonitor);
-        }
-        if (measurementMonitor != null) {
-            selectedMonitors.add(measurementMonitor);
-        }
-
-        done.close();
-        shm.getPowerMonitorReadings(selectedMonitors, null,
-                readings -> {
-                    mReadings = readings;
-                    done.open();
-                },
-                exception -> {
-                    mException = exception;
-                    done.open();
-                }
-        );
-        done.block();
-
-        assertThat(mException).isNull();
-
-        for (PowerMonitor monitor : selectedMonitors) {
-            assertThat(mReadings.getConsumedEnergyUws(monitor)).isAtLeast(0);
-            assertThat(mReadings.getTimestamp(monitor)).isGreaterThan(0);
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
index 101f7c2..5c411d5 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
@@ -31,6 +31,8 @@
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.util.Collections;
+
 /**
  * Unit test for {@link ContentCaptureManager}.
  *
@@ -69,7 +71,11 @@
         ContentCaptureOptions options =
                 createOptions(
                         new ContentCaptureOptions.ContentProtectionOptions(
-                                /* enableReceiver= */ false, BUFFER_SIZE));
+                                /* enableReceiver= */ false,
+                                BUFFER_SIZE,
+                                /* requiredGroups= */ Collections.emptyList(),
+                                /* optionalGroups= */ Collections.emptyList(),
+                                /* optionalGroupsThreshold= */ 0));
 
         ContentCaptureManager manager =
                 new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
@@ -82,7 +88,11 @@
         ContentCaptureOptions options =
                 createOptions(
                         new ContentCaptureOptions.ContentProtectionOptions(
-                                /* enableReceiver= */ true, /* bufferSize= */ 0));
+                                /* enableReceiver= */ true,
+                                /* bufferSize= */ 0,
+                                /* requiredGroups= */ Collections.emptyList(),
+                                /* optionalGroups= */ Collections.emptyList(),
+                                /* optionalGroupsThreshold= */ 0));
 
         ContentCaptureManager manager =
                 new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
@@ -95,7 +105,11 @@
         ContentCaptureOptions options =
                 createOptions(
                         new ContentCaptureOptions.ContentProtectionOptions(
-                                /* enableReceiver= */ true, BUFFER_SIZE));
+                                /* enableReceiver= */ true,
+                                BUFFER_SIZE,
+                                /* requiredGroups= */ Collections.emptyList(),
+                                /* optionalGroups= */ Collections.emptyList(),
+                                /* optionalGroupsThreshold= */ 0));
 
         ContentCaptureManager manager =
                 new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
index 3373b8b..e76d266 100644
--- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
@@ -47,6 +47,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -112,7 +113,11 @@
                 createOptions(
                         /* enableContentCaptureReceiver= */ true,
                         new ContentCaptureOptions.ContentProtectionOptions(
-                                /* enableReceiver= */ true, -BUFFER_SIZE));
+                                /* enableReceiver= */ true,
+                                -BUFFER_SIZE,
+                                /* requiredGroups= */ Collections.emptyList(),
+                                /* optionalGroups= */ Collections.emptyList(),
+                                /* optionalGroupsThreshold= */ 0));
         MainContentCaptureSession session = createSession(options);
         session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
 
@@ -313,7 +318,11 @@
         return createOptions(
                 enableContentCaptureReceiver,
                 new ContentCaptureOptions.ContentProtectionOptions(
-                        enableContentProtectionReceiver, BUFFER_SIZE));
+                        enableContentProtectionReceiver,
+                        BUFFER_SIZE,
+                        /* requiredGroups= */ Collections.emptyList(),
+                        /* optionalGroups= */ Collections.emptyList(),
+                        /* optionalGroupsThreshold= */ 0));
     }
 
     private ContentCaptureManager createManager(ContentCaptureOptions options) {
diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
index 263e563..6229530 100644
--- a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
+++ b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
@@ -31,6 +31,7 @@
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -154,7 +155,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).applyAsyncUnsafe();
     }
 
@@ -171,7 +173,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).applyAsyncUnsafe();
     }
 
@@ -241,7 +244,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).setEarlyWakeupStart();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
@@ -261,7 +265,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).setEarlyWakeupEnd();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
@@ -281,7 +286,8 @@
                     eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
             verify(mTransaction).setFrameRateCategory(
                     eq(mDefaultDisplayRoot),
-                    eq(FRAME_RATE_CATEGORY_DEFAULT));
+                    eq(FRAME_RATE_CATEGORY_DEFAULT),
+                    eq(false));
             verify(mTransaction).setEarlyWakeupEnd();
             verify(mTransaction).applyAsyncUnsafe();
             verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
@@ -299,7 +305,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).setEarlyWakeupStart();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
@@ -310,7 +317,7 @@
                 mHinter.startSession(HINT_ALL, DEFAULT_DISPLAY_ID, TEST_OTHER_REASON);
         // Verify we never call SF and perf manager since session1 is already running
         verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt());
-        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt());
+        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean());
         verify(mTransaction, never()).setEarlyWakeupEnd();
         verify(mTransaction, never()).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
@@ -318,7 +325,7 @@
         session2.close();
         // Verify we have not cleaned up because session1 is still running
         verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt());
-        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt());
+        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean());
         verify(mTransaction, never()).setEarlyWakeupEnd();
         verify(mTransaction, never()).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
@@ -330,7 +337,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).setEarlyWakeupEnd();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
@@ -348,7 +356,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).setEarlyWakeupStart();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
@@ -363,7 +372,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mSecondaryDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction, never()).setEarlyWakeupStart();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
@@ -378,13 +388,15 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction, never()).setFrameRateSelectionStrategy(
                 eq(mSecondaryDisplayRoot),
                 anyInt());
         verify(mTransaction, never()).setFrameRateCategory(
                 eq(mSecondaryDisplayRoot),
-                anyInt());
+                anyInt(),
+                eq(false));
         verify(mTransaction, never()).setEarlyWakeupEnd();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
@@ -401,7 +413,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mSecondaryDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).setEarlyWakeupEnd();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
diff --git a/graphics/java/Android.bp b/graphics/java/Android.bp
index 63d1f6d..db37a387 100644
--- a/graphics/java/Android.bp
+++ b/graphics/java/Android.bp
@@ -7,6 +7,12 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+aconfig_declarations {
+    name: "framework_graphics_flags",
+    package: "com.android.graphics.flags",
+    srcs: ["android/framework_graphics.aconfig"],
+}
+
 filegroup {
     name: "framework-graphics-nonupdatable-sources",
     srcs: [
diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig
new file mode 100644
index 0000000..e030dad
--- /dev/null
+++ b/graphics/java/android/framework_graphics.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.graphics.flags"
+
+flag {
+     name: "exact_compute_bounds"
+     namespace: "framework_graphics"
+     description: "Add a function without unused exact param for computeBounds."
+     bug: "304478551"
+}
\ No newline at end of file
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index b5fb13d..0a6fb84 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -16,11 +16,14 @@
 
 package android.graphics;
 
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.graphics.hwui.flags.Flags;
+
 import libcore.util.NativeAllocationRegistry;
 
 /**
@@ -125,6 +128,7 @@
      * Creates a new gainmap using the provided gainmap as the metadata source and the provided
      * bitmap as the replacement for the gainmapContents
      */
+    @FlaggedApi(Flags.FLAG_GAINMAP_CONSTRUCTOR_WITH_METADATA)
     public Gainmap(@NonNull Gainmap gainmap, @NonNull Bitmap gainmapContents) {
         this(gainmapContents, nCreateCopy(gainmap.mNativePtr));
     }
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index c9c1b23..deb89fa 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -16,11 +16,14 @@
 
 package android.graphics;
 
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Size;
 
+import com.android.graphics.flags.Flags;
+
 import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
 
@@ -309,6 +312,7 @@
      *
      * @param bounds Returns the computed bounds of the path's control points.
      */
+    @FlaggedApi(Flags.FLAG_EXACT_COMPUTE_BOUNDS)
     public void computeBounds(@NonNull RectF bounds) {
         nComputeBounds(mNativePath, bounds);
     }
diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java
index 4461f39..c2a7a84 100644
--- a/graphics/java/android/graphics/drawable/RippleShader.java
+++ b/graphics/java/android/graphics/drawable/RippleShader.java
@@ -109,9 +109,8 @@
             + "    float alpha = min(fadeIn, 1. - fadeOutNoise);\n"
             + "    vec2 uv = p * in_resolutionScale;\n"
             + "    vec2 densityUv = uv - mod(uv, in_noiseScale);\n"
-            + "    float turbulence = turbulence(uv, in_turbulencePhase);\n"
-            + "    float sparkleAlpha = sparkles(densityUv, in_noisePhase) * ring * alpha "
-            + "* turbulence;\n"
+            + "    float turb = turbulence(uv, in_turbulencePhase);\n"
+            + "    float sparkleAlpha = sparkles(densityUv, in_noisePhase) * ring * alpha * turb;\n"
             + "    float fade = min(fadeIn, 1. - fadeOutRipple);\n"
             + "    float waveAlpha = softCircle(p, center, in_maxRadius * scaleIn, 1.) * fade "
             + "* in_color.a;\n"
diff --git a/packages/SystemUI/ktfmt_includes.txt b/ktfmt_includes.txt
similarity index 99%
rename from packages/SystemUI/ktfmt_includes.txt
rename to ktfmt_includes.txt
index d3254b7..e4bf4c2 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/ktfmt_includes.txt
@@ -1,3 +1,4 @@
++services/permission
 +packages/SystemUI
 -packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
 -packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 5dfba5e..14a040a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -203,6 +203,7 @@
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             ShellController shellController,
+            DisplayInsetsController displayInsetsController,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopTasksController> desktopTasksController,
@@ -218,6 +219,7 @@
                     taskOrganizer,
                     displayController,
                     shellController,
+                    displayInsetsController,
                     syncQueue,
                     transitions,
                     desktopTasksController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 8a64037..c508d55 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -17,13 +17,19 @@
 package com.android.wm.shell.dagger.pip;
 
 import android.annotation.NonNull;
+import android.content.Context;
 
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.dagger.WMShellBaseModule;
 import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip2.phone.PipController;
 import com.android.wm.shell.pip2.phone.PipTransition;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
@@ -42,8 +48,21 @@
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
             @NonNull Transitions transitions,
             PipBoundsState pipBoundsState,
-            PipBoundsAlgorithm pipBoundsAlgorithm) {
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipController pipController) {
         return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
                 pipBoundsAlgorithm);
     }
+
+    @WMSingleton
+    @Provides
+    static PipController providePipController(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
+            DisplayController displayController,
+            DisplayInsetsController displayInsetsController,
+            PipDisplayLayoutState pipDisplayLayoutState) {
+        return PipController.create(context, shellInit, shellController, displayController,
+                displayInsetsController, pipDisplayLayoutState);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
new file mode 100644
index 0000000..186cb61
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -0,0 +1,133 @@
+/*
+ * 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.wm.shell.pip2.phone;
+
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.view.InsetsState;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+
+/**
+ * Manages the picture-in-picture (PIP) UI and states for Phones.
+ */
+public class PipController implements ConfigurationChangeListener,
+        DisplayController.OnDisplaysChangedListener {
+    private static final String TAG = PipController.class.getSimpleName();
+
+    private Context mContext;
+    private ShellController mShellController;
+    private DisplayController mDisplayController;
+    private DisplayInsetsController mDisplayInsetsController;
+    private PipDisplayLayoutState mPipDisplayLayoutState;
+
+    private PipController(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
+            DisplayController displayController,
+            DisplayInsetsController displayInsetsController,
+            PipDisplayLayoutState pipDisplayLayoutState) {
+        mContext = context;
+        mShellController = shellController;
+        mDisplayController = displayController;
+        mDisplayInsetsController = displayInsetsController;
+        mPipDisplayLayoutState = pipDisplayLayoutState;
+
+        if (PipUtils.isPip2ExperimentEnabled()) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
+    }
+
+    private void onInit() {
+        // Ensure that we have the display info in case we get calls to update the bounds before the
+        // listener calls back
+        mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId());
+        DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
+        mPipDisplayLayoutState.setDisplayLayout(layout);
+
+        mShellController.addConfigurationChangeListener(this);
+        mDisplayController.addDisplayWindowListener(this);
+        mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
+                new DisplayInsetsController.OnInsetsChangedListener() {
+                    @Override
+                    public void insetsChanged(InsetsState insetsState) {
+                        onDisplayChanged(mDisplayController
+                                        .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
+                    }
+                });
+    }
+
+    /**
+     * Instantiates {@link PipController}, returns {@code null} if the feature not supported.
+     */
+    public static PipController create(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
+            DisplayController displayController,
+            DisplayInsetsController displayInsetsController,
+            PipDisplayLayoutState pipDisplayLayoutState) {
+        if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
+            ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Device doesn't support Pip feature", TAG);
+            return null;
+        }
+        return new PipController(context, shellInit, shellController, displayController,
+                displayInsetsController, pipDisplayLayoutState);
+    }
+
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfiguration) {
+        mPipDisplayLayoutState.onConfigurationChanged();
+    }
+
+    @Override
+    public void onThemeChanged() {
+        onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay()));
+    }
+
+    @Override
+    public void onDisplayAdded(int displayId) {
+        if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+            return;
+        }
+        onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+    }
+
+    @Override
+    public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+        if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+            return;
+        }
+        onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+    }
+
+    private void onDisplayChanged(DisplayLayout layout) {
+        mPipDisplayLayoutState.setDisplayLayout(layout);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 226fe08..451e618 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -345,6 +345,8 @@
                     // Keyguard handler cannot handle it, process through original mixed
                     mActiveTransitions.remove(keyguardMixed);
                 }
+            } else if (mPipHandler != null) {
+                mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
index 87c438a..ba0ef20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
@@ -19,13 +19,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.IBinder;
+import android.util.Slog;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
 
-import java.util.ArrayList;
-
 /**
  * A Simple handler that tracks SLEEP transitions. We track them specially since we (ab)use these
  * as sentinels for fast-forwarding through animations when the screen is off.
@@ -34,30 +33,25 @@
  * don't register it like a normal handler.
  */
 class SleepHandler implements Transitions.TransitionHandler {
-    final ArrayList<IBinder> mSleepTransitions = new ArrayList<>();
-
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        mSleepTransitions.remove(transition);
-        startTransaction.apply();
-        finishCallback.onTransitionFinished(null);
-        return true;
+        if (info.hasChangesOrSideEffects()) {
+            Slog.e(Transitions.TAG, "Real changes included in a SLEEP transition");
+            return false;
+        } else {
+            startTransaction.apply();
+            finishCallback.onTransitionFinished(null);
+            return true;
+        }
     }
 
     @Override
     @Nullable
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
             @NonNull TransitionRequestInfo request) {
-        mSleepTransitions.add(transition);
         return new WindowContainerTransaction();
     }
-
-    @Override
-    public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
-            @Nullable SurfaceControl.Transaction finishTransaction) {
-        mSleepTransitions.remove(transition);
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 82fc0f4..aff35a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -231,4 +231,9 @@
     int getCaptionHeightId(@WindowingMode int windowingMode) {
         return R.dimen.freeform_decor_caption_height;
     }
+
+    @Override
+    int getCaptionViewId() {
+        return R.id.caption;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index bf99ab3..ca91d58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -21,6 +21,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.WindowInsets.Type.statusBars;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -50,6 +51,8 @@
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.InputMonitor;
+import android.view.InsetsSource;
+import android.view.InsetsState;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
@@ -67,6 +70,7 @@
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
@@ -131,6 +135,8 @@
     private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener =
             new DesktopModeKeyguardChangeListener();
     private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+    private final DisplayInsetsController mDisplayInsetsController;
+    private boolean mInImmersiveMode;
 
     public DesktopModeWindowDecorViewModel(
             Context context,
@@ -141,6 +147,7 @@
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             ShellController shellController,
+            DisplayInsetsController displayInsetsController,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopTasksController> desktopTasksController,
@@ -156,6 +163,7 @@
                 taskOrganizer,
                 displayController,
                 shellController,
+                displayInsetsController,
                 syncQueue,
                 transitions,
                 desktopTasksController,
@@ -176,6 +184,7 @@
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             ShellController shellController,
+            DisplayInsetsController displayInsetsController,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopTasksController> desktopTasksController,
@@ -191,6 +200,7 @@
         mTaskOrganizer = taskOrganizer;
         mShellController = shellController;
         mDisplayController = displayController;
+        mDisplayInsetsController = displayInsetsController;
         mSyncQueue = syncQueue;
         mTransitions = transitions;
         mDesktopTasksController = desktopTasksController;
@@ -213,6 +223,8 @@
             }
         });
         mShellCommandHandler.addDumpCallback(this::dump, this);
+        mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
+                new DesktopModeOnInsetsChangedListener());
     }
 
     @Override
@@ -655,9 +667,9 @@
     private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
         final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev);
         if (DesktopModeStatus.isEnabled()) {
-            if (relevantDecor == null
+            if (!mInImmersiveMode && (relevantDecor == null
                     || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM
-                    || mTransitionDragActive) {
+                    || mTransitionDragActive)) {
                 handleCaptionThroughStatusBar(ev, relevantDecor);
             }
         }
@@ -1051,6 +1063,35 @@
             return mIsKeyguardVisible && mIsKeyguardOccluded;
         }
     }
+
+    @VisibleForTesting
+    class DesktopModeOnInsetsChangedListener implements
+            DisplayInsetsController.OnInsetsChangedListener {
+        @Override
+        public void insetsChanged(InsetsState insetsState) {
+            for (int i = 0; i < insetsState.sourceSize(); i++) {
+                final InsetsSource source = insetsState.sourceAt(i);
+                if (source.getType() != statusBars()) {
+                    continue;
+                }
+
+                final DesktopModeWindowDecoration decor = getFocusedDecor();
+                if (decor == null) {
+                    return;
+                }
+                // If status bar inset is visible, top task is not in immersive mode
+                final boolean inImmersiveMode = !source.isVisible();
+                // Calls WindowDecoration#relayout if decoration visibility needs to be updated
+                if (inImmersiveMode != mInImmersiveMode) {
+                    decor.relayout(decor.mTaskInfo);
+                    mInImmersiveMode = inImmersiveMode;
+                }
+
+                return;
+            }
+        }
+    }
+
 }
 
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 380b59e..248e837 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -638,6 +638,11 @@
         return loadDimensionPixelSize(mContext.getResources(), getCaptionHeightId(windowingMode));
     }
 
+    @Override
+    int getCaptionViewId() {
+        return R.id.desktop_mode_caption;
+    }
+
     /**
      * Add transition to mTransitionsPausingRelayout
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 335a588..0548a8e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowInsets.Type.statusBars;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration.WindowingMode;
@@ -30,6 +31,8 @@
 import android.graphics.Rect;
 import android.os.Binder;
 import android.view.Display;
+import android.view.InsetsSource;
+import android.view.InsetsState;
 import android.view.LayoutInflater;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
@@ -119,6 +122,7 @@
     private WindowlessWindowManager mCaptionWindowManager;
     private SurfaceControlViewHost mViewHost;
     private Configuration mWindowDecorConfig;
+    private boolean mIsCaptionVisible;
 
     private final Binder mOwner = new Binder();
     private final Rect mCaptionInsetsRect = new Rect();
@@ -225,6 +229,8 @@
                     .inflate(params.mLayoutResId, null);
         }
 
+        updateCaptionVisibility(outResult.mRootView, mTaskInfo.displayId);
+
         final Resources resources = mDecorWindowContext.getResources();
         final Configuration taskConfig = mTaskInfo.getConfiguration();
         final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
@@ -272,12 +278,20 @@
 
             // Caption insets
             mCaptionInsetsRect.set(taskBounds);
-            mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
-            wct.addInsetsSource(mTaskInfo.token,
-                    mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
-            wct.addInsetsSource(mTaskInfo.token,
-                    mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
-                    mCaptionInsetsRect);
+            if (mIsCaptionVisible) {
+                mCaptionInsetsRect.bottom =
+                        mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
+                wct.addInsetsSource(mTaskInfo.token,
+                        mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
+                wct.addInsetsSource(mTaskInfo.token,
+                        mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
+                        mCaptionInsetsRect);
+            } else {
+                wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */,
+                        WindowInsets.Type.captionBar());
+                wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */,
+                        WindowInsets.Type.mandatorySystemGestures());
+            }
         } else {
             startT.hide(mCaptionContainerSurface);
         }
@@ -348,10 +362,41 @@
         }
     }
 
+    /**
+     * Checks if task has entered/exited immersive mode and requires a change in caption visibility.
+     */
+    private void updateCaptionVisibility(View rootView, int displayId) {
+        final InsetsState insetsState = mDisplayController.getInsetsState(displayId);
+        for (int i = 0; i < insetsState.sourceSize(); i++) {
+            final InsetsSource source = insetsState.sourceAt(i);
+            if (source.getType() != statusBars()) {
+                continue;
+            }
+
+            mIsCaptionVisible = source.isVisible();
+            setCaptionVisibility(rootView, mIsCaptionVisible);
+
+            return;
+        }
+    }
+
+    private void setCaptionVisibility(View rootView, boolean visible) {
+        if (rootView == null) {
+            return;
+        }
+        final int v = visible ? View.VISIBLE : View.GONE;
+        final View captionView = rootView.findViewById(getCaptionViewId());
+        captionView.setVisibility(v);
+    }
+
     int getCaptionHeightId(@WindowingMode int windowingMode) {
         return Resources.ID_NULL;
     }
 
+    int getCaptionViewId() {
+        return Resources.ID_NULL;
+    }
+
     /**
      * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or
      * registers {@link #mOnDisplaysChangedListener} if it doesn't.
@@ -466,7 +511,8 @@
      */
     public void addCaptionInset(WindowContainerTransaction wct) {
         final int captionHeightId = getCaptionHeightId(mTaskInfo.getWindowingMode());
-        if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL) {
+        if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL
+                || !mIsCaptionVisible) {
             return;
         }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
index b13e9a2..1df1136 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
@@ -81,9 +81,7 @@
         <option name="shell-timeout" value="6600s"/>
         <option name="test-timeout" value="6000s"/>
         <option name="hidden-api-checks" value="false"/>
-        <!-- TODO(b/288396763): re-enable when PerfettoListener is fixed
         <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
-        -->
         <!-- PerfettoListener related arguments -->
         <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
         <option name="instrumentation-arg"
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index b9c9049..da83d4c0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -1169,7 +1169,7 @@
     }
 
     @Test
-    public void testEmptyTransitionStillReportsKeyguardGoingAway() {
+    public void testEmptyTransition_withKeyguardGoingAway_plays() {
         Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
 
@@ -1188,6 +1188,65 @@
     }
 
     @Test
+    public void testSleepTransition_withKeyguardGoingAway_plays(){
+        Transitions transitions = createTestTransitions();
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        IBinder transitToken = new Binder();
+        transitions.requestStartTransition(transitToken,
+                new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */));
+
+        // Make a no-op transition
+        TransitionInfo info = new TransitionInfoBuilder(
+                TRANSIT_SLEEP, TRANSIT_FLAG_KEYGUARD_GOING_AWAY, true /* noOp */).build();
+        transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+                new StubTransaction());
+
+        // If keyguard-going-away flag set, then it shouldn't be aborted.
+        assertEquals(1, mDefaultHandler.activeCount());
+    }
+
+    @Test
+    public void testSleepTransition_withChanges_plays(){
+        Transitions transitions = createTestTransitions();
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        IBinder transitToken = new Binder();
+        transitions.requestStartTransition(transitToken,
+                new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */));
+
+        // Make a transition with some changes
+        TransitionInfo info = new TransitionInfoBuilder(TRANSIT_SLEEP)
+                .addChange(TRANSIT_OPEN).build();
+        info.setTrack(0);
+        transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+                new StubTransaction());
+
+        // If there is an actual change, then it shouldn't be aborted.
+        assertEquals(1, mDefaultHandler.activeCount());
+    }
+
+
+    @Test
+    public void testSleepTransition_empty_SyncBySleepHandler() {
+        Transitions transitions = createTestTransitions();
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        IBinder transitToken = new Binder();
+        transitions.requestStartTransition(transitToken,
+                new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */));
+
+        // Make a no-op transition
+        TransitionInfo info = new TransitionInfoBuilder(
+                TRANSIT_SLEEP, 0x0, true /* noOp */).build();
+        transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+                new StubTransaction());
+
+        // If there is nothing to actually play, it should not be offered to handlers.
+        assertEquals(0, mDefaultHandler.activeCount());
+    }
+
+    @Test
     public void testMultipleTracks() {
         Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 8eaf5a0..57aa47e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -33,8 +33,12 @@
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.InputChannel
 import android.view.InputMonitor
+import android.view.InsetsSource
+import android.view.InsetsState
 import android.view.SurfaceControl
 import android.view.SurfaceView
+import android.view.WindowInsets.Type.navigationBars
+import android.view.WindowInsets.Type.statusBars
 import androidx.core.content.getSystemService
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -42,6 +46,7 @@
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayInsetsController
 import com.android.wm.shell.common.DisplayLayout
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
@@ -53,10 +58,12 @@
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
@@ -68,6 +75,7 @@
 import java.util.Optional
 import java.util.function.Supplier
 
+
 /** Tests of [DesktopModeWindowDecorViewModel]  */
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -80,6 +88,7 @@
     @Mock private lateinit var mockTaskOrganizer: ShellTaskOrganizer
     @Mock private lateinit var mockDisplayController: DisplayController
     @Mock private lateinit var mockDisplayLayout: DisplayLayout
+    @Mock private lateinit var displayInsetsController: DisplayInsetsController
     @Mock private lateinit var mockSyncQueue: SyncTransactionQueue
     @Mock private lateinit var mockDesktopTasksController: DesktopTasksController
     @Mock private lateinit var mockInputMonitor: InputMonitor
@@ -97,6 +106,7 @@
     }
 
     private lateinit var shellInit: ShellInit
+    private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
     private lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel
 
     @Before
@@ -111,6 +121,7 @@
                 mockTaskOrganizer,
                 mockDisplayController,
                 mockShellController,
+                displayInsetsController,
                 mockSyncQueue,
                 mockTransitions,
                 Optional.of(mockDesktopTasksController),
@@ -131,6 +142,11 @@
         whenever(mockInputMonitor.inputChannel).thenReturn(inputChannels[1])
 
         shellInit.init()
+
+        val listenerCaptor =
+                argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>()
+        verify(displayInsetsController).addInsetsChangedListener(anyInt(), listenerCaptor.capture())
+        desktopModeOnInsetsChangedListener = listenerCaptor.firstValue
     }
 
     @Test
@@ -274,6 +290,67 @@
         verify(decoration).addTransitionPausingRelayout(transition)
     }
 
+    @Test
+    fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() {
+        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
+        val decoration = setUpMockDecorationForTask(task)
+
+        onTaskOpening(task)
+
+        // Add status bar insets source
+        val insetsState = InsetsState()
+        val statusBarInsetsSourceId = 0
+        val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars())
+        statusBarInsetsSource.isVisible = false
+        insetsState.addSource(statusBarInsetsSource)
+
+        desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+
+        // Verify relayout occurs when status bar inset visibility changes
+        verify(decoration, times(1)).relayout(task)
+    }
+
+    @Test
+    fun testRelayoutDoesNotRunWhenNonStatusBarsInsetsSourceVisibilityChanges() {
+        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
+        val decoration = setUpMockDecorationForTask(task)
+
+        onTaskOpening(task)
+
+        // Add navigation bar insets source
+        val insetsState = InsetsState()
+        val navigationBarInsetsSourceId = 1
+        val navigationBarInsetsSource = InsetsSource(navigationBarInsetsSourceId, navigationBars())
+        navigationBarInsetsSource.isVisible = false
+        insetsState.addSource(navigationBarInsetsSource)
+
+        desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+
+        // Verify relayout does not occur when non-status bar inset changes visibility
+        verify(decoration, never()).relayout(task)
+    }
+
+    @Test
+    fun testRelayoutDoesNotRunWhenNonStatusBarsInsetSourceVisibilityDoesNotChange() {
+        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
+        val decoration = setUpMockDecorationForTask(task)
+
+        onTaskOpening(task)
+
+        // Add status bar insets source
+        val insetsState = InsetsState()
+        val statusBarInsetsSourceId = 0
+        val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars())
+        statusBarInsetsSource.isVisible = false
+        insetsState.addSource(statusBarInsetsSource)
+
+        desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+        desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+
+        // Verify relayout runs only once when status bar inset visibility changes.
+        verify(decoration, times(1)).relayout(task)
+    }
+
     private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
         desktopModeWindowDecorViewModel.onTaskOpening(
                 task,
@@ -313,6 +390,8 @@
         whenever(mockDesktopModeWindowDecorFactory.create(
                 any(), any(), any(), eq(task), any(), any(), any(), any(), any())
         ).thenReturn(decoration)
+        decoration.mTaskInfo = task
+        whenever(decoration.isFocused).thenReturn(task.isFocused)
         return decoration
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index fcb7863..8061aa3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -18,6 +18,9 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowInsets.Type.captionBar;
+import static android.view.WindowInsets.Type.mandatorySystemGestures;
+import static android.view.WindowInsets.Type.statusBars;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlBuilder;
@@ -25,6 +28,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.Assert.assertTrue;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -51,6 +56,7 @@
 import android.util.DisplayMetrics;
 import android.view.AttachedSurfaceControl;
 import android.view.Display;
+import android.view.InsetsState;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
 import android.view.View;
@@ -94,6 +100,7 @@
     private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400);
     private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60);
     private static final int CORNER_RADIUS = 20;
+    private static final int STATUS_BAR_INSET_SOURCE_ID = 0;
 
     private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
             new WindowDecoration.RelayoutResult<>();
@@ -118,6 +125,7 @@
     private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions =
             new ArrayList<>();
     private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
+    private final InsetsState mInsetsState = new InsetsState();
     private SurfaceControl.Transaction mMockSurfaceControlStartT;
     private SurfaceControl.Transaction mMockSurfaceControlFinishT;
     private SurfaceControl.Transaction mMockSurfaceControlAddWindowT;
@@ -141,6 +149,11 @@
                 .create(any(), any(), any());
         when(mMockSurfaceControlViewHost.getRootSurfaceControl())
                 .thenReturn(mMockRootSurfaceControl);
+        when(mMockView.findViewById(anyInt())).thenReturn(mMockView);
+
+        // Add status bar inset so that WindowDecoration does not think task is in immersive mode
+        mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()).setVisible(true);
+        doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
     }
 
     @Test
@@ -537,12 +550,41 @@
 
         windowDecor.relayout(taskInfo);
 
-        verify(mMockSurfaceControlStartT).setColor(taskSurface, new float[] {1.f, 1.f, 0.f});
+        verify(mMockSurfaceControlStartT).setColor(taskSurface, new float[]{1.f, 1.f, 0.f});
 
         mockitoSession.finishMocking();
     }
 
     @Test
+    public void testInsetsAddedWhenCaptionIsVisible() {
+        final Display defaultDisplay = mock(Display.class);
+        doReturn(defaultDisplay).when(mMockDisplayController)
+                .getDisplay(Display.DEFAULT_DISPLAY);
+
+        final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+                new ActivityManager.TaskDescription.Builder();
+        final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .setTaskDescriptionBuilder(taskDescriptionBuilder)
+                .setVisible(true)
+                .build();
+        final SurfaceControl taskSurface = mock(SurfaceControl.class);
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+
+        assertTrue(mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars())
+                .isVisible());
+        assertTrue(mInsetsState.sourceSize() == 1);
+        assertTrue(mInsetsState.sourceAt(0).getType() == statusBars());
+
+        windowDecor.relayout(taskInfo);
+
+        verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
+                eq(0) /* index */, eq(captionBar()), any());
+        verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
+                eq(0) /* index */, eq(mandatorySystemGestures()), any());
+    }
+
+    @Test
     public void testRelayout_fluidResizeEnabled_fullscreenTask_clearTaskSurfaceColor() {
         StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
                 DesktopModeStatus.class).strictness(LENIENT).startMocking();
@@ -581,6 +623,33 @@
         mockitoSession.finishMocking();
     }
 
+
+    @Test
+    public void testInsetsRemovedWhenCaptionIsHidden() {
+        final Display defaultDisplay = mock(Display.class);
+        doReturn(defaultDisplay).when(mMockDisplayController)
+                .getDisplay(Display.DEFAULT_DISPLAY);
+
+        mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(false);
+
+        final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+                new ActivityManager.TaskDescription.Builder();
+        final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .setTaskDescriptionBuilder(taskDescriptionBuilder)
+                .setVisible(true)
+                .build();
+        final SurfaceControl taskSurface = mock(SurfaceControl.class);
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+
+        windowDecor.relayout(taskInfo);
+
+        verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
+                eq(0) /* index */, eq(captionBar()));
+        verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
+                eq(0) /* index */, eq(mandatorySystemGestures()));
+    }
+
     private TestWindowDecoration createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
         return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index d0d3c5e..e672b98 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -20,3 +20,10 @@
   description: "APIs to help enable animations involving gainmaps"
   bug: "296482289"
 }
+
+flag {
+  name: "gainmap_constructor_with_metadata"
+  namespace: "core_graphics"
+  description: "APIs to create a new gainmap with a bitmap for metadata."
+  bug: "304478551"
+}
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 90850d3..22c5862 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -26,6 +26,7 @@
 #include <gui/TraceUtils.h>
 #include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
+#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
 #include <ui/FatVector.h>
 #include <vk/GrVkExtensions.h>
 #include <vk/GrVkTypes.h>
@@ -435,7 +436,7 @@
     options.fContextDeleteContext = this;
     options.fContextDeleteProc = onGrContextReleased;
 
-    return GrDirectContext::MakeVulkan(backendContext, options);
+    return GrDirectContexts::MakeVulkan(backendContext, options);
 }
 
 VkFunctorInitParams VulkanManager::getVkFunctorInitParams() const {
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index ceb3858..b002bbf 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -1282,10 +1282,8 @@
          *    {@link AudioFormat#CHANNEL_OUT_BACK_CENTER},
          *    {@link AudioFormat#CHANNEL_OUT_SIDE_LEFT},
          *    {@link AudioFormat#CHANNEL_OUT_SIDE_RIGHT}.
-         *    <p> For a valid {@link AudioTrack} channel position mask,
-         *    the following conditions apply:
-         *    <br> (1) at most eight channel positions may be used;
-         *    <br> (2) right/left pairs should be matched.
+         *    <p> For output or {@link AudioTrack}, channel position masks which do not contain
+         *    matched left/right pairs are invalid.
          *    <p> For input or {@link AudioRecord}, the mask should be
          *    {@link AudioFormat#CHANNEL_IN_MONO} or
          *    {@link AudioFormat#CHANNEL_IN_STEREO}.  {@link AudioFormat#CHANNEL_IN_MONO} is
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index d9ed6a8..5b88079 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1473,8 +1473,7 @@
      * Returns the volume group id associated to the given {@link AudioAttributes}.
      *
      * @param attributes The {@link AudioAttributes} to consider.
-     * @return {@link android.media.audiopolicy.AudioVolumeGroup} id supporting the given
-     * {@link AudioAttributes} if found,
+     * @return audio volume group id supporting the given {@link AudioAttributes} if found,
      * {@code android.media.audiopolicy.AudioVolumeGroup.DEFAULT_VOLUME_GROUP} otherwise.
      */
     public int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) {
@@ -1589,7 +1588,7 @@
      * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)} to retrieve
      * the volume group id supporting the given {@link AudioAttributes}.
      *
-     * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
+     * @param groupId of the audio volume group to consider.
      * @param direction The direction to adjust the volume. One of
      *            {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or
      *            {@link #ADJUST_SAME}.
@@ -1633,8 +1632,8 @@
      * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)} to retrieve
      * the volume group id supporting the given {@link AudioAttributes}.
      *
-     * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
-     * @return The mute state for the given {@link android.media.audiopolicy.AudioVolumeGroup} id.
+     * @param groupId of the audio volume group to consider.
+     * @return The mute state for the given audio volume group id.
      * @see #adjustVolumeGroupVolume(int, int, int)
      */
     public boolean isVolumeGroupMuted(int groupId) {
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 8c63580..2169090 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -463,7 +463,7 @@
     /**
      * Returns the current {@link RouteListingPreference} of the target router.
      *
-     * <p>If this instance was created using {@link #getInstance(Context, String)}, then it returns
+     * <p>If this instance was created using {@code #getInstance(Context, String)}, then it returns
      * the last {@link RouteListingPreference} set by the process this router was created for.
      *
      * @see #setRouteListingPreference(RouteListingPreference)
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index a354f91..c9cfa67 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -27,3 +27,10 @@
     description: "Disables the broadcast receiver that prevents scanning when the screen is off."
     bug: "304234628"
 }
+
+flag {
+    namespace: "media_solutions"
+    name: "fallback_to_default_handling_when_media_session_has_fixed_volume_handling"
+    description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller."
+    bug: "293743975"
+}
diff --git a/media/java/android/media/midi/MidiUmpDeviceService.java b/media/java/android/media/midi/MidiUmpDeviceService.java
index 6e2aaab..bbbe7f6 100644
--- a/media/java/android/media/midi/MidiUmpDeviceService.java
+++ b/media/java/android/media/midi/MidiUmpDeviceService.java
@@ -38,7 +38,7 @@
  * of {@link MidiReceiver}s for sending data out the output ports.
  *
  * Unlike traditional MIDI byte streams, only complete UMPs should be sent.
- * Unlike with {@link #MidiDeviceService}, the number of input and output ports must be equal.
+ * Unlike with {@link MidiDeviceService}, the number of input and output ports must be equal.
  *
  * <p>To extend this class, you must declare the service in your manifest file with
  * an intent filter with the {@link #SERVICE_INTERFACE} action
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index d294601..80e2247 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -156,4 +156,24 @@
             + ".permission.MANAGE_MEDIA_PROJECTION)")
     void setUserReviewGrantedConsentResult(ReviewGrantedConsentResult consentResult,
             in @nullable IMediaProjection projection);
+
+    /**
+     * Notifies system server that we are handling a particular state during the consent flow.
+     *
+     * <p>Only used for emitting atoms.
+     *
+     * @param hostUid               The uid of the process requesting consent to capture, may be an app or
+     *                              SystemUI.
+     * @param state                 The state that SystemUI is handling during the consent flow.
+     *                              Must be a valid
+     *                              state defined in the MediaProjectionState enum.
+     * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED.
+     *                              Indicates the entry point for requesting the permission. Must be
+     *                              a valid state defined
+     *                              in the SessionCreationSource enum.
+     */
+    @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+            + ".permission.MANAGE_MEDIA_PROJECTION)")
+    void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource);
 }
diff --git a/media/java/android/media/projection/OWNERS b/media/java/android/media/projection/OWNERS
index cc9be9c..880ec8f 100644
--- a/media/java/android/media/projection/OWNERS
+++ b/media/java/android/media/projection/OWNERS
@@ -4,3 +4,4 @@
 santoscordon@google.com
 chaviw@google.com
 nmusgrave@google.com
+dakinola@google.com
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 13f7743..2db4be8 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -940,9 +940,7 @@
                 isHardwareInput = true;
                 hdmiConnectionRelativePosition = getRelativePosition(mContext, mHdmiDeviceInfo);
                 isConnectedToHdmiSwitch = hdmiConnectionRelativePosition
-                                != HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW
-                        && hdmiConnectionRelativePosition
-                                != HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN;
+                                == HdmiUtils.HDMI_RELATIVE_POSITION_BELOW;
             } else if (mTvInputHardwareInfo != null) {
                 id = generateInputId(componentName, mTvInputHardwareInfo);
                 type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER);
diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp
index 58bcd1d..e71597a 100644
--- a/omapi/aidl/Android.bp
+++ b/omapi/aidl/Android.bp
@@ -24,6 +24,11 @@
     backend: {
         java: {
             sdk_version: "module_current",
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.nfcservices",
+            ],
+
         },
         rust: {
             enabled: true,
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index 222877b..88f12046 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -102,6 +102,8 @@
         <item name="android:layout_height">36dp</item>
         <item name="android:textAllCaps">false</item>
         <item name="android:textSize">14sp</item>
+        <item name="android:paddingLeft">6dp</item>
+        <item name="android:paddingRight">6dp</item>
         <item name="android:background">@drawable/btn_negative_multiple_devices</item>
         <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
     </style>
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 4c313b2..3409c29 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -16,6 +16,8 @@
 
 package com.android.externalstorage;
 
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.usage.StorageStatsManager;
@@ -64,7 +66,19 @@
 import java.util.Locale;
 import java.util.Objects;
 import java.util.UUID;
+import java.util.regex.Pattern;
 
+/**
+ * Presents content of the shared (a.k.a. "external") storage.
+ * <p>
+ * Starting with Android 11 (R), restricts access to the certain sections of the shared storage:
+ * {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/}, that will be hidden in
+ * the DocumentsUI by default.
+ * See <a href="https://developer.android.com/about/versions/11/privacy/storage">
+ * Storage updates in Android 11</a>.
+ * <p>
+ * Documents ID format: {@code root:path/to/file}.
+ */
 public class ExternalStorageProvider extends FileSystemProvider {
     private static final String TAG = "ExternalStorage";
 
@@ -75,7 +89,12 @@
     private static final Uri BASE_URI =
             new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build();
 
-    // docId format: root:path/to/file
+    /**
+     * Regex for detecting {@code /Android/data/}, {@code /Android/obb/} and
+     * {@code /Android/sandbox/} along with all their subdirectories and content.
+     */
+    private static final Pattern PATTERN_RESTRICTED_ANDROID_SUBTREES =
+            Pattern.compile("^Android/(?:data|obb|sandbox)(?:/.+)?", CASE_INSENSITIVE);
 
     private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
             Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
@@ -278,76 +297,91 @@
         return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
     }
 
+    /**
+     * Mark {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/} on the
+     * integrated shared ("external") storage along with all their content and subdirectories as
+     * hidden.
+     */
     @Override
-    public Cursor queryChildDocumentsForManage(
-            String parentDocId, String[] projection, String sortOrder)
-            throws FileNotFoundException {
-        return queryChildDocumentsShowAll(parentDocId, projection, sortOrder);
+    protected boolean shouldHideDocument(@NonNull String documentId) {
+        // Don't need to hide anything on USB drives.
+        if (isOnRemovableUsbStorage(documentId)) {
+            return false;
+        }
+
+        final String path = getPathFromDocId(documentId);
+        return PATTERN_RESTRICTED_ANDROID_SUBTREES.matcher(path).matches();
     }
 
     /**
      * Check that the directory is the root of storage or blocked file from tree.
+     * <p>
+     * Note, that this is different from hidden documents: blocked documents <b>WILL</b> appear
+     * the UI, but the user <b>WILL NOT</b> be able to select them.
      *
-     * @param docId the docId of the directory to be checked
+     * @param documentId the docId of the directory to be checked
      * @return true, should be blocked from tree. Otherwise, false.
+     *
+     * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE
      */
     @Override
-    protected boolean shouldBlockFromTree(@NonNull String docId) {
-        try {
-            final File dir = getFileForDocId(docId, false /* visible */);
-
-            // the file is null or it is not a directory
-            if (dir == null || !dir.isDirectory()) {
-                return false;
-            }
-
-            // Allow all directories on USB, including the root.
-            try {
-                RootInfo rootInfo = getRootFromDocId(docId);
-                if ((rootInfo.flags & Root.FLAG_REMOVABLE_USB) == Root.FLAG_REMOVABLE_USB) {
-                    return false;
-                }
-            } catch (FileNotFoundException e) {
-                Log.e(TAG, "Failed to determine rootInfo for docId");
-            }
-
-            final String path = getPathFromDocId(docId);
-
-            // Block the root of the storage
-            if (path.isEmpty()) {
-                return true;
-            }
-
-            // Block Download folder from tree
-            if (TextUtils.equals(Environment.DIRECTORY_DOWNLOADS.toLowerCase(Locale.ROOT),
-                    path.toLowerCase(Locale.ROOT))) {
-                return true;
-            }
-
-            // Block /Android
-            if (TextUtils.equals(Environment.DIRECTORY_ANDROID.toLowerCase(Locale.ROOT),
-                    path.toLowerCase(Locale.ROOT))) {
-                return true;
-            }
-
-            // Block /Android/data, /Android/obb, /Android/sandbox and sub dirs
-            if (shouldHide(dir)) {
-                return true;
-            }
-
+    protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId)
+            throws FileNotFoundException {
+        final File dir = getFileForDocId(documentId, false);
+        // The file is null or it is not a directory
+        if (dir == null || !dir.isDirectory()) {
             return false;
-        } catch (IOException e) {
-            throw new IllegalArgumentException(
-                    "Failed to determine if " + docId + " should block from tree " + ": " + e);
         }
+
+        // Allow all directories on USB, including the root.
+        if (isOnRemovableUsbStorage(documentId)) {
+            return false;
+        }
+
+        // Get canonical(!) path. Note that this path will have neither leading nor training "/".
+        // This the root's path will be just an empty string.
+        final String path = getPathFromDocId(documentId);
+
+        // Block the root of the storage
+        if (path.isEmpty()) {
+            return true;
+        }
+
+        // Block /Download/ and /Android/ folders from the tree.
+        if (equalIgnoringCase(path, Environment.DIRECTORY_DOWNLOADS) ||
+                equalIgnoringCase(path, Environment.DIRECTORY_ANDROID)) {
+            return true;
+        }
+
+        // This shouldn't really make a difference, but just in case - let's block hidden
+        // directories as well.
+        if (shouldHideDocument(documentId)) {
+            return true;
+        }
+
+        return false;
     }
 
+    private boolean isOnRemovableUsbStorage(@NonNull String documentId) {
+        final RootInfo rootInfo;
+        try {
+            rootInfo = getRootFromDocId(documentId);
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "Failed to determine rootInfo for docId\"" + documentId + '"');
+            return false;
+        }
+
+        return (rootInfo.flags & Root.FLAG_REMOVABLE_USB) != 0;
+    }
+
+    @NonNull
     @Override
-    protected String getDocIdForFile(File file) throws FileNotFoundException {
+    protected String getDocIdForFile(@NonNull File file) throws FileNotFoundException {
         return getDocIdForFileMaybeCreate(file, false);
     }
 
-    private String getDocIdForFileMaybeCreate(File file, boolean createNewDir)
+    @NonNull
+    private String getDocIdForFileMaybeCreate(@NonNull File file, boolean createNewDir)
             throws FileNotFoundException {
         String path = file.getAbsolutePath();
 
@@ -417,31 +451,33 @@
     private File getFileForDocId(String docId, boolean visible, boolean mustExist)
             throws FileNotFoundException {
         RootInfo root = getRootFromDocId(docId);
-        return buildFile(root, docId, visible, mustExist);
+        return buildFile(root, docId, mustExist);
     }
 
-    private Pair<RootInfo, File> resolveDocId(String docId, boolean visible)
-            throws FileNotFoundException {
+    private Pair<RootInfo, File> resolveDocId(String docId) throws FileNotFoundException {
         RootInfo root = getRootFromDocId(docId);
-        return Pair.create(root, buildFile(root, docId, visible, true));
+        return Pair.create(root, buildFile(root, docId, /* mustExist */ true));
     }
 
     @VisibleForTesting
-    static String getPathFromDocId(String docId) throws IOException {
+    static String getPathFromDocId(String docId) {
         final int splitIndex = docId.indexOf(':', 1);
         final String docIdPath = docId.substring(splitIndex + 1);
-        // Get CanonicalPath and remove the first "/"
-        final String canonicalPath = new File(docIdPath).getCanonicalPath().substring(1);
 
-        if (canonicalPath.isEmpty()) {
-            return canonicalPath;
+        // Canonicalize path and strip the leading "/"
+        final String path;
+        try {
+            path = new File(docIdPath).getCanonicalPath().substring(1);
+        } catch (IOException e) {
+            Log.w(TAG, "Could not canonicalize \"" + docIdPath + '"');
+            return "";
         }
 
-        // remove trailing "/"
-        if (canonicalPath.charAt(canonicalPath.length() - 1) == '/') {
-            return canonicalPath.substring(0, canonicalPath.length() - 1);
+        // Remove the trailing "/" as well.
+        if (!path.isEmpty() && path.charAt(path.length() - 1) == '/') {
+            return path.substring(0, path.length() - 1);
         } else {
-            return canonicalPath;
+            return path;
         }
     }
 
@@ -460,7 +496,7 @@
         return root;
     }
 
-    private File buildFile(RootInfo root, String docId, boolean visible, boolean mustExist)
+    private File buildFile(RootInfo root, String docId, boolean mustExist)
             throws FileNotFoundException {
         final int splitIndex = docId.indexOf(':', 1);
         final String path = docId.substring(splitIndex + 1);
@@ -544,7 +580,7 @@
     @Override
     public Path findDocumentPath(@Nullable String parentDocId, String childDocId)
             throws FileNotFoundException {
-        final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId, false);
+        final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId);
         final RootInfo root = resolvedDocId.first;
         File child = resolvedDocId.second;
 
@@ -648,6 +684,13 @@
         }
     }
 
+    /**
+     * Print the state into the given stream.
+     * Gets invoked when you run:
+     * <pre>
+     * adb shell dumpsys activity provider com.android.externalstorage/.ExternalStorageProvider
+     * </pre>
+     */
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ", 160);
@@ -731,4 +774,8 @@
         }
         return bundle;
     }
+
+    private static boolean equalIgnoringCase(@NonNull String a, @NonNull String b) {
+        return TextUtils.equals(a.toLowerCase(Locale.ROOT), b.toLowerCase(Locale.ROOT));
+    }
 }
diff --git a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java
index 18a8edc..0144b6e 100644
--- a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java
+++ b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java
@@ -16,47 +16,64 @@
 
 package com.android.externalstorage;
 
+import static android.provider.DocumentsContract.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID;
+
 import static com.android.externalstorage.ExternalStorageProvider.AUTHORITY;
 import static com.android.externalstorage.ExternalStorageProvider.getPathFromDocId;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.app.Instrumentation;
+import android.content.Context;
 import android.content.pm.ProviderInfo;
 
+import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
 public class ExternalStorageProviderTest {
+
+    @NonNull
+    private static final Instrumentation sInstrumentation =
+            InstrumentationRegistry.getInstrumentation();
+    @NonNull
+    private static final Context sTargetContext = sInstrumentation.getTargetContext();
+
+    private ExternalStorageProvider mExternalStorageProvider;
+
+    @Before
+    public void setUp() {
+        mExternalStorageProvider = new ExternalStorageProvider();
+    }
+
+
     @Test
-    public void onCreate_shouldUpdateVolumes() throws Exception {
-        ExternalStorageProvider externalStorageProvider = new ExternalStorageProvider();
-        ExternalStorageProvider spyProvider = spy(externalStorageProvider);
-        ProviderInfo providerInfo = new ProviderInfo();
+    public void onCreate_shouldUpdateVolumes() {
+        final ExternalStorageProvider spyProvider = spy(mExternalStorageProvider);
+
+        final ProviderInfo providerInfo = new ProviderInfo();
         providerInfo.authority = AUTHORITY;
         providerInfo.grantUriPermissions = true;
         providerInfo.exported = true;
 
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                spyProvider.attachInfoForTesting(
-                        InstrumentationRegistry.getTargetContext(), providerInfo);
-            }
-        });
+        sInstrumentation.runOnMainSync(() ->
+                spyProvider.attachInfoForTesting(sTargetContext, providerInfo));
 
         verify(spyProvider, atLeast(1)).updateVolumes();
     }
 
     @Test
-    public void testGetPathFromDocId() throws Exception {
+    public void test_getPathFromDocId() {
         final String root = "root";
         final String path = "abc/def/ghi";
         String docId = root + ":" + path;
@@ -79,4 +96,62 @@
         docId = root + ":" + twoDotPath;
         assertEquals(getPathFromDocId(docId), path);
     }
+
+    @Test
+    public void test_shouldHideDocument() {
+        // Should hide "Android/data", "Android/obb", "Android/sandbox" and all their
+        // "subtrees".
+        final String[] shouldHide = {
+                // "Android/data" and all its subdirectories
+                "Android/data",
+                "Android/data/com.my.app",
+                "Android/data/com.my.app/cache",
+                "Android/data/com.my.app/cache/image.png",
+                "Android/data/mydata",
+
+                // "Android/obb" and all its subdirectories
+                "Android/obb",
+                "Android/obb/com.my.app",
+                "Android/obb/com.my.app/file.blob",
+
+                // "Android/sandbox" and all its subdirectories
+                "Android/sandbox",
+                "Android/sandbox/com.my.app",
+
+                // Also make sure we are not allowing path traversals
+                "Android/./data",
+                "Android/Download/../data",
+        };
+        for (String path : shouldHide) {
+            final String docId = buildDocId(path);
+            assertTrue("ExternalStorageProvider should hide \"" + docId + "\", but it didn't",
+                    mExternalStorageProvider.shouldHideDocument(docId));
+        }
+
+        // Should NOT hide anything else.
+        final String[] shouldNotHide = {
+                "Android",
+                "Android/datadir",
+                "Documents",
+                "Download",
+                "Music",
+                "Pictures",
+        };
+        for (String path : shouldNotHide) {
+            final String docId = buildDocId(path);
+            assertFalse("ExternalStorageProvider should NOT hide \"" + docId + "\", but it did",
+                    mExternalStorageProvider.shouldHideDocument(docId));
+        }
+    }
+
+    @NonNull
+    private static String buildDocId(@NonNull String path) {
+        return buildDocId(EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID, path);
+    }
+
+    @NonNull
+    private static String buildDocId(@NonNull String root, @NonNull String path) {
+        // docId format: root:path/to/file
+        return root + ':' + path;
+    }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
index 8de12d0..976a3ad 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
@@ -172,7 +172,7 @@
 
         private Bitmap getBitmapFromDrawable(Drawable drawable) {
             // Create an empty bitmap with the dimensions of our drawable
-            Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+            final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
                     drawable.getIntrinsicHeight(),
                     Bitmap.Config.ARGB_8888);
             // Associate it with a canvas. This canvas will draw the icon on the bitmap
@@ -183,7 +183,11 @@
 
             // Scale it down if the icon is too large
             if ((bmp.getWidth() > iconSize * 2) || (bmp.getHeight() > iconSize * 2)) {
-                bmp = Bitmap.createScaledBitmap(bmp, iconSize, iconSize, true);
+                Bitmap scaledBitmap = Bitmap.createScaledBitmap(bmp, iconSize, iconSize, true);
+                if (scaledBitmap != bmp) {
+                    bmp.recycle();
+                }
+                return scaledBitmap;
             }
             return bmp;
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 1bb00b3..ce0772f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -54,6 +54,7 @@
 import com.android.internal.util.UserIcons;
 import com.android.launcher3.icons.BaseIconFactory.IconOptions;
 import com.android.launcher3.icons.IconFactory;
+import com.android.launcher3.util.UserIconInfo;
 import com.android.settingslib.drawable.UserIconDrawable;
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.settingslib.utils.BuildCompatUtils;
@@ -597,15 +598,25 @@
 
     /** Get the corresponding adaptive icon drawable. */
     public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) {
-        UserManager um = context.getSystemService(UserManager.class);
-        boolean isClone = um.getProfiles(user.getIdentifier()).stream()
-                .anyMatch(profile ->
-                        profile.isCloneProfile() && profile.id == user.getIdentifier());
+        int userType = UserIconInfo.TYPE_MAIN;
+        try {
+            UserInfo ui = context.getSystemService(UserManager.class).getUserInfo(
+                    user.getIdentifier());
+            if (ui != null) {
+                if (ui.isCloneProfile()) {
+                    userType = UserIconInfo.TYPE_CLONED;
+                } else if (ui.isManagedProfile()) {
+                    userType = UserIconInfo.TYPE_WORK;
+                }
+            }
+        } catch (Exception e) {
+            // Ignore
+        }
         try (IconFactory iconFactory = IconFactory.obtain(context)) {
             return iconFactory
                     .createBadgedIconBitmap(
                             icon,
-                            new IconOptions().setUser(user).setIsCloneProfile(isClone))
+                            new IconOptions().setUser(new UserIconInfo(user, userType)))
                     .newIcon(context);
         }
     }
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index f65f5a3..4954cb4 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -62,6 +62,7 @@
             static_libs: [
                 "CommunalLayoutLib",
                 "PlatformComposeCore",
+                "PlatformComposeSceneTransitionLayout",
 
                 "androidx.compose.runtime_runtime",
                 "androidx.compose.material3_material3",
@@ -164,7 +165,6 @@
         "SystemUISharedLib",
         "SystemUI-statsd",
         "SettingsLib",
-        "com_android_systemui_communal_flags_lib",
         "com_android_systemui_flags_lib",
         "androidx.core_core-ktx",
         "androidx.viewpager2_viewpager2",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 9bfc4be..5881631 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -83,6 +83,7 @@
     <uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/>
     <uses-permission android:name="android.permission.READ_WIFI_CREDENTIAL"/>
     <uses-permission android:name="android.permission.LOCATION_HARDWARE" />
+    <uses-permission android:name="android.permission.NETWORK_FACTORY" />
     <!-- Physical hardware -->
     <uses-permission android:name="android.permission.MANAGE_USB" />
     <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
@@ -1062,5 +1063,9 @@
             <meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer"
                 tools:node="remove" />
         </provider>
+
+        <!-- Allow SystemUI to listen for the capabilities defined in the linked xml -->
+        <property android:name="android.net.PROPERTY_SELF_CERTIFIED_CAPABILITIES"
+                  android:value="@xml/self_certified_network_capabilities_both" />
     </application>
 </manifest>
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index b18c790..dc4208e 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -2,8 +2,7 @@
     name: "com_android_systemui_flags",
     package: "com.android.systemui",
     srcs: [
-        "systemui.aconfig",
-        "accessibility.aconfig",
+        "*.aconfig",
     ],
 }
 
@@ -11,16 +10,3 @@
     name: "com_android_systemui_flags_lib",
     aconfig_declarations: "com_android_systemui_flags",
 }
-
-aconfig_declarations {
-    name: "com_android_systemui_communal_flags",
-    package: "com.android.systemui.communal",
-    srcs: [
-        "communal.aconfig",
-    ],
-}
-
-java_aconfig_library {
-    name: "com_android_systemui_communal_flags_lib",
-    aconfig_declarations: "com_android_systemui_communal_flags",
-}
diff --git a/packages/SystemUI/aconfig/communal.aconfig b/packages/SystemUI/aconfig/communal.aconfig
index 8ecb984..2c6ff97 100644
--- a/packages/SystemUI/aconfig/communal.aconfig
+++ b/packages/SystemUI/aconfig/communal.aconfig
@@ -1,4 +1,4 @@
-package: "com.android.systemui.communal"
+package: "com.android.systemui"
 
 flag {
     name: "communal_hub"
diff --git a/packages/SystemUI/communal/layout/tests/Android.bp b/packages/SystemUI/communal/layout/tests/Android.bp
index a60b1de..9a05504 100644
--- a/packages/SystemUI/communal/layout/tests/Android.bp
+++ b/packages/SystemUI/communal/layout/tests/Android.bp
@@ -32,7 +32,7 @@
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
         "testables",
-        "truth-prebuilt",
+        "truth",
     ],
     libs: [
         "android.test.mock",
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index e4426fe..796abf4b 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -33,6 +33,7 @@
     static_libs: [
         "SystemUI-core",
         "PlatformComposeCore",
+        "PlatformComposeSceneTransitionLayout",
 
         "androidx.compose.runtime_runtime",
         "androidx.compose.animation_animation-graphics",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 591fa76..4bbb78b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -371,7 +371,8 @@
             val iconContainer = StatusIconContainer(context, null)
             val iconManager = createTintedIconManager(iconContainer, StatusBarLocation.QS)
             iconManager.setTint(
-                Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
+                Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary),
+                Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimaryInverse),
             )
             statusBarIconController.addIconGroup(iconManager)
 
diff --git a/packages/SystemUI/compose/scene/Android.bp b/packages/SystemUI/compose/scene/Android.bp
new file mode 100644
index 0000000..050d1d5
--- /dev/null
+++ b/packages/SystemUI/compose/scene/Android.bp
@@ -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 {
+    // 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"],
+}
+
+android_library {
+    name: "PlatformComposeSceneTransitionLayout",
+    manifest: "AndroidManifest.xml",
+
+    srcs: [
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.material3_material3",
+    ],
+
+    kotlincflags: ["-Xjvm-default=all"],
+    use_resource_processor: true,
+}
diff --git a/packages/SystemUI/compose/scene/AndroidManifest.xml b/packages/SystemUI/compose/scene/AndroidManifest.xml
new file mode 100644
index 0000000..81131bb
--- /dev/null
+++ b/packages/SystemUI/compose/scene/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.compose.animation.scene">
+
+
+</manifest>
diff --git a/packages/SystemUI/compose/scene/OWNERS b/packages/SystemUI/compose/scene/OWNERS
new file mode 100644
index 0000000..33a59c2
--- /dev/null
+++ b/packages/SystemUI/compose/scene/OWNERS
@@ -0,0 +1,13 @@
+set noparent
+
+# Bug component: 1184816
+
+jdemeulenaere@google.com
+omarmt@google.com
+
+# SysUI Dr No's.
+# Don't send reviews here.
+dsandler@android.com
+cinek@google.com
+juliacr@google.com
+pixel@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/TEST_MAPPING b/packages/SystemUI/compose/scene/TEST_MAPPING
new file mode 100644
index 0000000..f644a23
--- /dev/null
+++ b/packages/SystemUI/compose/scene/TEST_MAPPING
@@ -0,0 +1,48 @@
+{
+  "presubmit": [
+    {
+      "name": "PlatformComposeSceneTransitionLayoutTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "PlatformComposeCoreTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "SystemUIComposeFeaturesTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "SystemUIComposeGalleryTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
similarity index 71%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 566967f..041fc48 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -17,12 +17,10 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.DisposableEffectResult
-import androidx.compose.runtime.DisposableEffectScope
 import androidx.compose.runtime.State
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.lerp
 import androidx.compose.ui.unit.Dp
@@ -45,6 +43,20 @@
 }
 
 /**
+ * Animate a shared Int value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedIntAsState(
+    value: Int,
+    debugName: String,
+    canOverflow: Boolean = true,
+): State<Int> {
+    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow)
+}
+
+/**
  * Animate a shared Float value.
  *
  * @see SceneScope.animateSharedValueAsState
@@ -60,6 +72,20 @@
 }
 
 /**
+ * Animate a shared Float value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedFloatAsState(
+    value: Float,
+    debugName: String,
+    canOverflow: Boolean = true,
+): State<Float> {
+    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow)
+}
+
+/**
  * Animate a shared Dp value.
  *
  * @see SceneScope.animateSharedValueAsState
@@ -75,6 +101,20 @@
 }
 
 /**
+ * Animate a shared Dp value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedDpAsState(
+    value: Dp,
+    debugName: String,
+    canOverflow: Boolean = true,
+): State<Dp> {
+    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow)
+}
+
+/**
  * Animate a shared Color value.
  *
  * @see SceneScope.animateSharedValueAsState
@@ -88,6 +128,19 @@
     return animateSharedValueAsState(value, key, element, ::lerp, canOverflow = false)
 }
 
+/**
+ * Animate a shared Color value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedColorAsState(
+    value: Color,
+    debugName: String,
+): State<Color> {
+    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow = false)
+}
+
 @Composable
 internal fun <T> animateSharedValueAsState(
     layoutImpl: SceneTransitionLayoutImpl,
@@ -98,33 +151,22 @@
     lerp: (T, T, Float) -> T,
     canOverflow: Boolean,
 ): State<T> {
-    val sharedValue = remember(key) { Element.SharedValue(key, value) }
+    val sharedValue =
+        Snapshot.withoutReadObservation {
+            element.sceneValues.getValue(scene.key).sharedValues.getOrPut(key) {
+                Element.SharedValue(key, value)
+            } as Element.SharedValue<T>
+        }
+
     if (value != sharedValue.value) {
         sharedValue.value = value
     }
 
-    DisposableEffect(element, scene, sharedValue) {
-        addSharedValueToElement(element, scene, sharedValue)
-    }
-
     return remember(layoutImpl, element, sharedValue, lerp, canOverflow) {
         derivedStateOf { computeValue(layoutImpl, element, sharedValue, lerp, canOverflow) }
     }
 }
 
-private fun <T> DisposableEffectScope.addSharedValueToElement(
-    element: Element,
-    scene: Scene,
-    sharedValue: Element.SharedValue<T>,
-): DisposableEffectResult {
-    val sceneValues =
-        element.sceneValues[scene.key] ?: error("Element $element is not present in $scene")
-    val sharedValues = sceneValues.sharedValues
-
-    sharedValues[sharedValue.key] = sharedValue
-    return onDispose { sharedValues.remove(sharedValue.key) }
-}
-
 private fun <T> computeValue(
     layoutImpl: SceneTransitionLayoutImpl,
     element: Element,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
similarity index 89%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index 11bbf2a..6dbeb69 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -21,6 +21,7 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.Modifier
@@ -36,8 +37,6 @@
 
 private const val TAG = "MovableElement"
 
-private object MovableElementScopeImpl : MovableElementScope
-
 @Composable
 internal fun MovableElement(
     layoutImpl: SceneTransitionLayoutImpl,
@@ -51,6 +50,10 @@
         // every time an element is added/removed from SceneTransitionLayoutImpl.elements, so we
         // disable read observation during the look-up in that map.
         val element = Snapshot.withoutReadObservation { layoutImpl.elements.getValue(key) }
+        val movableElementScope =
+            remember(layoutImpl, element, scene) {
+                MovableElementScopeImpl(layoutImpl, element, scene)
+            }
 
         // 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
@@ -77,7 +80,7 @@
                     }
                 }
             ) {
-                element.movableContent { MovableElementScopeImpl.content() }
+                element.movableContent { movableElementScope.content() }
             }
         } else {
             // If we are not composed, we draw the previous drawing commands at the same size as the
@@ -178,3 +181,20 @@
         isHighestScene
     }
 }
+
+private class MovableElementScopeImpl(
+    private val layoutImpl: SceneTransitionLayoutImpl,
+    private val element: Element,
+    private val scene: Scene,
+) : MovableElementScope {
+    @Composable
+    override fun <T> animateSharedValueAsState(
+        value: T,
+        debugName: String,
+        lerp: (start: T, stop: T, fraction: Float) -> T,
+        canOverflow: Boolean,
+    ): State<T> {
+        val key = remember { ValueKey(debugName) }
+        return animateSharedValueAsState(layoutImpl, scene, element, key, value, lerp, canOverflow)
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
similarity index 96%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 4283c0e..74e66d2 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -160,7 +160,16 @@
 
 // TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey
 // arguments to allow sharing values inside a movable element.
-@ElementDsl interface MovableElementScope
+@ElementDsl
+interface MovableElementScope {
+    @Composable
+    fun <T> animateSharedValueAsState(
+        value: T,
+        debugName: String,
+        lerp: (start: T, stop: T, fraction: Float) -> T,
+        canOverflow: Boolean,
+    ): State<T>
+}
 
 /** An action performed by the user. */
 sealed interface UserAction
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt b/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/modifiers/ConditionalModifiers.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/modifiers/ConditionalModifiers.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
diff --git a/packages/SystemUI/compose/scene/tests/Android.bp b/packages/SystemUI/compose/scene/tests/Android.bp
new file mode 100644
index 0000000..b53fae2
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/Android.bp
@@ -0,0 +1,50 @@
+// 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"],
+}
+
+android_test {
+    name: "PlatformComposeSceneTransitionLayoutTests",
+    manifest: "AndroidManifest.xml",
+    test_suites: ["device-tests"],
+    sdk_version: "current",
+    certificate: "platform",
+
+    srcs: [
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "PlatformComposeSceneTransitionLayout",
+
+        "androidx.test.runner",
+        "androidx.test.ext.junit",
+
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.ui_ui-test-junit4",
+        "androidx.compose.ui_ui-test-manifest",
+
+        "truth",
+    ],
+
+    kotlincflags: ["-Xjvm-default=all"],
+    use_resource_processor: true,
+}
diff --git a/packages/SystemUI/compose/scene/tests/AndroidManifest.xml b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml
new file mode 100644
index 0000000..1a9172e
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.compose.animation.scene.tests" >
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.compose.animation.scene.tests"
+                     android:label="Tests for SceneTransitionLayout"/>
+
+</manifest>
\ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
new file mode 100644
index 0000000..7b7695e
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -0,0 +1,218 @@
+/*
+ * 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.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.lerp
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.lerp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.ui.util.lerp
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AnimatedSharedAsStateTest {
+    @get:Rule val rule = createComposeRule()
+
+    private data class Values(
+        val int: Int,
+        val float: Float,
+        val dp: Dp,
+        val color: Color,
+    )
+
+    private fun lerp(start: Values, stop: Values, fraction: Float): Values {
+        return Values(
+            int = lerp(start.int, stop.int, fraction),
+            float = lerp(start.float, stop.float, fraction),
+            dp = lerp(start.dp, stop.dp, fraction),
+            color = lerp(start.color, stop.color, fraction),
+        )
+    }
+
+    @Composable
+    private fun SceneScope.Foo(
+        targetValues: Values,
+        onCurrentValueChanged: (Values) -> Unit,
+    ) {
+        val key = TestElements.Foo
+        Box(Modifier.element(key)) {
+            val int by animateSharedIntAsState(targetValues.int, TestValues.Value1, key)
+            val float by animateSharedFloatAsState(targetValues.float, TestValues.Value2, key)
+            val dp by animateSharedDpAsState(targetValues.dp, TestValues.Value3, key)
+            val color by animateSharedColorAsState(targetValues.color, TestValues.Value4, key)
+
+            // Make sure we read the values during composition, so that we recompose and call
+            // onCurrentValueChanged() with the latest values.
+            val currentValues = Values(int, float, dp, color)
+            SideEffect { onCurrentValueChanged(currentValues) }
+        }
+    }
+
+    @Composable
+    private fun SceneScope.MovableFoo(
+        targetValues: Values,
+        onCurrentValueChanged: (Values) -> Unit,
+    ) {
+        val key = TestElements.Foo
+        MovableElement(key = key, Modifier) {
+            val int by
+                animateSharedIntAsState(targetValues.int, debugName = TestValues.Value1.debugName)
+            val float by
+                animateSharedFloatAsState(
+                    targetValues.float,
+                    debugName = TestValues.Value2.debugName
+                )
+            val dp by
+                animateSharedDpAsState(targetValues.dp, debugName = TestValues.Value3.debugName)
+            val color by
+                animateSharedColorAsState(
+                    targetValues.color,
+                    debugName = TestValues.Value4.debugName
+                )
+
+            // Make sure we read the values during composition, so that we recompose and call
+            // onCurrentValueChanged() with the latest values.
+            val currentValues = Values(int, float, dp, color)
+            SideEffect { onCurrentValueChanged(currentValues) }
+        }
+    }
+
+    @Test
+    fun animateSharedValues() {
+        val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red)
+        val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue)
+
+        var lastValueInFrom = fromValues
+        var lastValueInTo = toValues
+
+        rule.testTransition(
+            fromSceneContent = {
+                Foo(targetValues = fromValues, onCurrentValueChanged = { lastValueInFrom = it })
+            },
+            toSceneContent = {
+                Foo(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it })
+            },
+            transition = {
+                // The transition lasts 64ms = 4 frames.
+                spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
+            },
+            fromScene = TestScenes.SceneA,
+            toScene = TestScenes.SceneB,
+        ) {
+            before {
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+
+                // to was not composed yet, so lastValueInTo was not set yet.
+                assertThat(lastValueInTo).isEqualTo(toValues)
+            }
+
+            at(16) {
+                // Given that we use Modifier.element() here, animateSharedXAsState is composed in
+                // both scenes and values should be interpolated with the transition fraction.
+                val expectedValues = lerp(fromValues, toValues, fraction = 0.25f)
+                assertThat(lastValueInFrom).isEqualTo(expectedValues)
+                assertThat(lastValueInTo).isEqualTo(expectedValues)
+            }
+
+            at(32) {
+                val expectedValues = lerp(fromValues, toValues, fraction = 0.5f)
+                assertThat(lastValueInFrom).isEqualTo(expectedValues)
+                assertThat(lastValueInTo).isEqualTo(expectedValues)
+            }
+
+            at(48) {
+                val expectedValues = lerp(fromValues, toValues, fraction = 0.75f)
+                assertThat(lastValueInFrom).isEqualTo(expectedValues)
+                assertThat(lastValueInTo).isEqualTo(expectedValues)
+            }
+
+            after {
+                assertThat(lastValueInFrom).isEqualTo(toValues)
+                assertThat(lastValueInTo).isEqualTo(toValues)
+            }
+        }
+    }
+
+    @Test
+    fun movableAnimateSharedValues() {
+        val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red)
+        val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue)
+
+        var lastValueInFrom = fromValues
+        var lastValueInTo = toValues
+
+        rule.testTransition(
+            fromSceneContent = {
+                MovableFoo(
+                    targetValues = fromValues,
+                    onCurrentValueChanged = { lastValueInFrom = it }
+                )
+            },
+            toSceneContent = {
+                MovableFoo(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it })
+            },
+            transition = {
+                // The transition lasts 64ms = 4 frames.
+                spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
+            },
+            fromScene = TestScenes.SceneA,
+            toScene = TestScenes.SceneB,
+        ) {
+            before {
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+
+                // to was not composed yet, so lastValueInTo was not set yet.
+                assertThat(lastValueInTo).isEqualTo(toValues)
+            }
+
+            at(16) {
+                // Given that we use MovableElement here, animateSharedXAsState is composed only
+                // once, in the highest scene (in this case, in toScene).
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+                assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.25f))
+            }
+
+            at(32) {
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+                assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.5f))
+            }
+
+            at(48) {
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+                assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.75f))
+            }
+
+            after {
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+                assertThat(lastValueInTo).isEqualTo(toValues)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestTransition.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestTransition.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt
similarity index 94%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt
index 8357262..b4c393e 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt
@@ -37,6 +37,9 @@
 /** Value keys that can be reused by tests. */
 object TestValues {
     val Value1 = ValueKey("Value1")
+    val Value2 = ValueKey("Value2")
+    val Value3 = ValueKey("Value3")
+    val Value4 = ValueKey("Value4")
 }
 
 // We use a transition duration of 480ms here because it is a multiple of 16, the time of a frame in
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt
diff --git a/packages/SystemUI/compose/core/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
similarity index 99%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
index 2af3638..e94eff3 100644
--- a/packages/SystemUI/compose/core/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
@@ -32,7 +32,6 @@
 import com.android.compose.animation.scene.TestScenes
 import com.android.compose.animation.scene.inScene
 import com.android.compose.animation.scene.testTransition
-import com.android.compose.modifiers.size
 import com.android.compose.test.assertSizeIsEqualTo
 import com.android.compose.test.onEach
 import org.junit.Rule
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/Selectors.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/test/Selectors.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
index a5e5aaa..a9d2ee3 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
@@ -71,7 +71,11 @@
       */
     void applyDark(DarkReceiver object);
 
+    /** The default tint (applicable for dark backgrounds) is white */
     int DEFAULT_ICON_TINT = Color.WHITE;
+    /** To support an icon which wants to create contrast, the default tint is black-on-white. */
+    int DEFAULT_INVERSE_ICON_TINT = Color.BLACK;
+
     Rect sTmpRect = new Rect();
     int[] sTmpInt2 = new int[2];
 
@@ -88,6 +92,18 @@
     }
 
     /**
+     * @return the tint to apply to a foreground, given that the background is tinted
+     *         per {@link #getTint}
+     */
+    static int getInverseTint(Collection<Rect> tintAreas, View view, int inverseColor) {
+        if (isInAreas(tintAreas, view)) {
+            return inverseColor;
+        } else {
+            return DEFAULT_INVERSE_ICON_TINT;
+        }
+    }
+
+    /**
      * @return true if more than half of the view area are in any of the given
      *         areas, false otherwise
      */
@@ -129,7 +145,40 @@
      */
     @ProvidesInterface(version = DarkReceiver.VERSION)
     interface DarkReceiver {
-        int VERSION = 2;
+        int VERSION = 3;
+
+        /**
+         * @param areas list of regions on screen where the tint applies
+         * @param darkIntensity float representing the level of tint. In the range [0,1]
+         * @param tint the tint applicable as a foreground contrast to the dark regions. This value
+         *             is interpolated between a default light and dark tone, and is therefore
+         *             usable as-is, as long as the view is in one of the areas defined in
+         *             {@code areas}.
+         *
+         * @see DarkIconDispatcher#isInArea(Rect, View) for utilizing {@code areas}
+         *
+         * Note: only one of {@link #onDarkChanged(ArrayList, float, int)} or
+         * {@link #onDarkChangedWithContrast(ArrayList, int, int)} need to be implemented, as both
+         * will be called in the same circumstances.
+         */
         void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint);
+
+        /**
+         * New version of onDarkChanged, which describes a tint plus an optional contrastTint
+         * that can be used if the tint is applied to the background of an icon.
+         *
+         * We use the 2 here to avoid the case where an existing override of onDarkChanged
+         * might pass in parameters as bare numbers (e.g. 0 instead of 0f) which might get
+         * mistakenly cast to (int) and therefore trigger this method.
+         *
+         * @param areas list of areas where dark tint applies
+         * @param tint int describing the tint color to use
+         * @param contrastTint if desired, a contrasting color that can be used for a foreground
+         *
+         * Note: only one of {@link #onDarkChanged(ArrayList, float, int)} or
+         * {@link #onDarkChangedWithContrast(ArrayList, int, int)} need to be implemented, as both
+         * will be called in the same circumstances.
+         */
+        default void onDarkChangedWithContrast(ArrayList<Rect> areas, int tint, int contrastTint) {}
     }
 }
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
index be1e655..445bdc2 100644
--- a/packages/SystemUI/proguard_common.flags
+++ b/packages/SystemUI/proguard_common.flags
@@ -2,45 +2,17 @@
 
 # Needed to ensure callback field references are kept in their respective
 # owning classes when the downstream callback registrars only store weak refs.
-# TODO(b/264686688): Handle these cases with more targeted annotations.
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  private com.android.keyguard.KeyguardUpdateMonitorCallback *;
-  private com.android.systemui.privacy.PrivacyConfig$Callback *;
-  private com.android.systemui.privacy.PrivacyItemController$Callback *;
-  private com.android.systemui.settings.UserTracker$Callback *;
-  private com.android.systemui.statusbar.phone.StatusBarWindowCallback *;
-  private com.android.systemui.util.service.Observer$Callback *;
-  private com.android.systemui.util.service.ObservableServiceConnection$Callback *;
-}
-# Note that these rules are temporary companions to the above rules, required
-# for cases like Kotlin where fields with anonymous types use the anonymous type
-# rather than the supertype.
--if class * extends com.android.keyguard.KeyguardUpdateMonitorCallback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+# Note that we restrict this to SysUISingleton classes, as other registering
+# classes should either *always* unregister or *never* register from their
+# constructor. We also keep callback class names for easier debugging.
+-keepnames @com.android.systemui.util.annotations.WeaklyReferencedCallback class *
+-keepnames class * extends @com.android.systemui.util.annotations.WeaklyReferencedCallback **
+-if @com.android.systemui.util.annotations.WeaklyReferencedCallback class *
+-keepclassmembers,allowaccessmodification @com.android.systemui.dagger.SysUISingleton class * {
   <1> *;
 }
--if class * extends com.android.systemui.privacy.PrivacyConfig$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  <1> *;
-}
--if class * extends com.android.systemui.privacy.PrivacyItemController$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  <1> *;
-}
--if class * extends com.android.systemui.settings.UserTracker$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  <1> *;
-}
--if class * extends com.android.systemui.statusbar.phone.StatusBarWindowCallback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  <1> *;
-}
--if class * extends com.android.systemui.util.service.Observer$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  <1> *;
-}
--if class * extends com.android.systemui.util.service.ObservableServiceConnection$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+-if class * extends @com.android.systemui.util.annotations.WeaklyReferencedCallback **
+-keepclassmembers,allowaccessmodification @com.android.systemui.dagger.SysUISingleton class * {
   <1> *;
 }
 
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 a1e2dc3..a8017f6 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
@@ -52,15 +52,22 @@
                 android:visibility="gone"
                 />
         </FrameLayout>
-        <ImageView
-            android:id="@+id/mobile_type"
-            android:layout_height="@dimen/status_bar_mobile_type_size"
+        <FrameLayout
+            android:id="@+id/mobile_type_container"
+            android:layout_height="@dimen/status_bar_mobile_container_height"
             android:layout_width="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:adjustViewBounds="true"
-            android:paddingStart="2.5sp"
-            android:paddingEnd="1sp"
-            android:visibility="gone" />
+            android:layout_marginStart="2.5sp"
+            android:layout_marginEnd="1sp"
+            android:visibility="gone"
+            >
+            <ImageView
+                android:id="@+id/mobile_type"
+                android:layout_height="@dimen/status_bar_mobile_type_size"
+                android:layout_width="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:adjustViewBounds="true"
+                />
+        </FrameLayout>
         <Space
             android:id="@+id/mobile_roaming_space"
             android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/drawable/mobile_network_type_background.xml b/packages/SystemUI/res/drawable/mobile_network_type_background.xml
new file mode 100644
index 0000000..db25c89
--- /dev/null
+++ b/packages/SystemUI/res/drawable/mobile_network_type_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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.
+  ~
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle"
+    >
+    <corners
+        android:topLeftRadius="0dp"
+        android:topRightRadius="@dimen/status_bar_mobile_container_corner_radius"
+        android:bottomRightRadius="0dp"
+        android:bottomLeftRadius="@dimen/status_bar_mobile_container_corner_radius"/>
+    <solid android:color="#FFF" />
+    <padding
+        android:left="2sp"
+        android:right="2sp"/>
+</shape>
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
index a51c55e..8cfcb68 100644
--- a/packages/SystemUI/res/layout/connected_display_dialog.xml
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -15,8 +15,9 @@
   -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:id="@+id/cd_bottom_sheet"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_height="wrap_content"
     android:gravity="center_horizontal"
     android:orientation="vertical"
     android:paddingHorizontal="@dimen/dialog_side_padding"
@@ -26,11 +27,14 @@
 
     <ImageView
         android:id="@+id/connected_display_dialog_icon"
-        android:layout_width="@dimen/screenrecord_logo_size"
-        android:layout_height="@dimen/screenrecord_logo_size"
+        android:layout_width="@dimen/connected_display_dialog_logo_size"
+        android:layout_height="@dimen/connected_display_dialog_logo_size"
+        android:background="@drawable/circular_background"
+        android:backgroundTint="?androidprv:attr/materialColorPrimary"
         android:importantForAccessibility="no"
+        android:padding="6dp"
         android:src="@drawable/stat_sys_connected_display"
-        android:tint="?androidprv:attr/materialColorPrimary" />
+        android:tint="?androidprv:attr/materialColorOnPrimary" />
 
     <TextView
         android:id="@+id/connected_display_dialog_title"
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index ea3c012..1f671ac 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -37,6 +37,9 @@
 
     <bool name="config_use_large_screen_shade_header">true</bool>
 
+    <!-- Whether to show bottom sheets edge to edge -->
+    <bool name="config_edgeToEdgeBottomSheetDialog">false</bool>
+
     <!-- A collection of defaults for the quick affordances on the lock screen. Each item must be a
     string with two parts: the ID of the slot and the comma-delimited list of affordance IDs,
     separated by a colon ':' character. For example: <item>bottom_end:home,wallet</item>. The
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 1add90f..75e71e4 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -947,6 +947,9 @@
     <!-- Flag controlling whether visual query attention detection has been enabled. -->
     <bool name="config_enableVisualQueryAttentionDetection">false</bool>
 
+    <!-- Whether to show bottom sheets edge to edge -->
+    <bool name="config_edgeToEdgeBottomSheetDialog">true</bool>
+
     <!--
     Whether the scene container framework is enabled.
 
@@ -954,4 +957,15 @@
     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.
+    -->
+    <integer name="config_restToUnlockDuration">300</integer>
+
+    <!--
+    Width in pixels of the Side FPS sensor.
+    -->
+    <integer name="config_sfpsSensorWidth">200</integer>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 5a83c7d..1b09c6a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -177,6 +177,12 @@
     <!-- Size of the view displaying the mobile signal icon in the status bar. This value should
         match the viewport height of mobile signal drawables such as ic_lte_mobiledata -->
     <dimen name="status_bar_mobile_type_size">16sp</dimen>
+    <!-- Size of the view that contains the network type. Should be equal to
+    status_bar_mobile_type_size + 2, to account for 1sp top and bottom padding -->
+    <dimen name="status_bar_mobile_container_height">18sp</dimen>
+    <!-- Corner radius for the background of the network type indicator. Should be equal to
+        status_bar_mobile_container_height / 2 -->
+    <dimen name="status_bar_mobile_container_corner_radius">9sp</dimen>
     <!-- Size of the view displaying the mobile roam icon in the status bar. This value should
         match the viewport size of drawable stat_sys_roaming -->
     <dimen name="status_bar_mobile_roam_size">8sp</dimen>
@@ -1355,6 +1361,9 @@
     <dimen name="screenrecord_options_padding_bottom">16dp</dimen>
     <dimen name="screenrecord_buttons_margin_top">20dp</dimen>
 
+    <!-- Connected display dialog -->
+    <dimen name="connected_display_dialog_logo_size">48dp</dimen>
+
     <!-- Keyguard user switcher -->
     <dimen name="kg_user_switcher_text_size">16sp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 321594f..7e44adf 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -711,8 +711,8 @@
      <!-- QuickSettings: Cast detail panel, default device description [CHAR LIMIT=NONE] -->
     <!-- QuickSettings: Cast detail panel, text when there are no items [CHAR LIMIT=NONE] -->
     <string name="quick_settings_cast_detail_empty_text">No devices available</string>
-    <!-- QuickSettings: Cast unavailable, text when not connected to WiFi [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_cast_no_wifi">Wi\u2011Fi not connected</string>
+    <!-- QuickSettings: Cast unavailable, text when not connected to WiFi or ethernet[CHAR LIMIT=NONE] -->
+    <string name="quick_settings_cast_no_network">No Wi\u2011Fi or Ethernet connection</string>
     <!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] -->
     <string name="quick_settings_brightness_dialog_title">Brightness</string>
     <!-- QuickSettings: Label for the toggle that controls whether display inversion is enabled. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml b/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml
new file mode 100644
index 0000000..4b430e1
--- /dev/null
+++ b/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  ~
+  -->
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+</network-capabilities-declaration>
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
index 3360c96..aef8371 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -24,7 +24,7 @@
     val knownFlags: Map<String, Flag<*>>
         get() {
             // We need to access Flags in order to initialize our map.
-            assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+            assert(flagMap.contains(Flags.NULL_FLAG.name)) { "Where is the null flag?" }
             return flagMap
         }
 
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
index 75465c2..f4b4296 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
@@ -24,7 +24,7 @@
     val knownFlags: Map<String, Flag<*>>
         get() {
             // We need to access Flags in order to initialize our map.
-            assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+            assert(flagMap.contains(Flags.NULL_FLAG.name)) { "Where is the null flag?" }
             return flagMap
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index f6a0563..9bddcd7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -22,10 +22,10 @@
 
 import com.android.app.animation.Interpolators;
 import com.android.keyguard.dagger.KeyguardStatusViewScope;
-import com.android.systemui.res.R;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.plugins.ClockController;
+import com.android.systemui.res.R;
 import com.android.systemui.shared.clocks.DefaultClockController;
 
 import java.io.PrintWriter;
@@ -452,6 +452,10 @@
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
+        // TODO: b/305022530
+        if (mClock.getConfig().getId().equals("DIGITAL_CLOCK_METRO")) {
+            mClock.getEvents().onColorPaletteChanged(mContext.getResources());
+        }
 
         if (changed) {
             post(() -> updateClockTargetRegions());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 50be97e..3585feb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -44,8 +44,6 @@
 import android.view.WindowInsets;
 import android.view.WindowInsetsAnimationControlListener;
 import android.view.WindowInsetsAnimationController;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
@@ -66,18 +64,8 @@
  */
 public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
 
-    private final int mDisappearYTranslation;
-
-    private static final long IME_DISAPPEAR_DURATION_MS = 125;
-
-    // A delay constant to be used in a workaround for the situation where InputMethodManagerService
-    // is not switched to the new user yet.
-    // TODO: Remove this by ensuring such a race condition never happens.
-
     private TextView mPasswordEntry;
     private TextViewInputDisabler mPasswordEntryDisabler;
-    private Interpolator mLinearOutSlowInInterpolator;
-    private Interpolator mFastOutLinearInInterpolator;
     private DisappearAnimationListener mDisappearAnimationListener;
     @Nullable private MotionLayout mContainerMotionLayout;
     private boolean mAlreadyUsingSplitBouncer = false;
@@ -93,12 +81,6 @@
 
     public KeyguardPasswordView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mDisappearYTranslation = getResources().getDimensionPixelSize(
-                R.dimen.disappear_y_translation);
-        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
-                context, android.R.interpolator.linear_out_slow_in);
-        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
-                context, android.R.interpolator.fast_out_linear_in);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 8717a53..d2d0517 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -31,6 +31,7 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.view.WindowManager;
@@ -39,9 +40,9 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
 
 public class KeyguardSimPinViewController
         extends KeyguardPinBasedInputViewController<KeyguardSimPinView> {
@@ -324,7 +325,11 @@
         } else {
             SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId);
             CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash
-            msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
+            if (!TextUtils.isEmpty(displayName)) {
+                msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
+            } else {
+                msg = rez.getString(R.string.kg_sim_pin_instructions);
+            }
             if (info != null) {
                 color = info.getIconTint();
             }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 248b7af..b52a36b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -29,6 +29,7 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.WindowManager;
 import android.widget.ImageView;
@@ -36,9 +37,9 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
 
 public class KeyguardSimPukViewController
         extends KeyguardPinBasedInputViewController<KeyguardSimPukView> {
@@ -206,7 +207,11 @@
         } else {
             SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId);
             CharSequence displayName = info != null ? info.getDisplayName() : "";
-            msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
+            if (!TextUtils.isEmpty(displayName)) {
+                msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
+            } else {
+                msg = rez.getString(R.string.kg_puk_enter_puk_hint);
+            }
             if (info != null) {
                 color = info.getIconTint();
             }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 7b59632..2476067 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -25,12 +25,14 @@
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.plugins.WeatherData;
 import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.util.annotations.WeaklyReferencedCallback;
 
 import java.util.TimeZone;
 
 /**
  * Callback for general information relevant to lock screen.
  */
+@WeaklyReferencedCallback
 public class KeyguardUpdateMonitorCallback {
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt
index a590dccd..b9b2fd8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt
@@ -23,8 +23,6 @@
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
-import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
-import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
 import com.android.systemui.dagger.SysUISingleton
 import dagger.Binds
 import dagger.Module
@@ -49,10 +47,4 @@
     @Binds
     @SysUISingleton
     fun bindsLogContextInteractor(impl: LogContextInteractorImpl): LogContextInteractor
-
-    @Binds
-    @SysUISingleton
-    fun providesSideFpsOverlayInteractor(
-        impl: SideFpsOverlayInteractorImpl
-    ): SideFpsOverlayInteractor
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
index f36a3ec..a317a06 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -54,6 +54,9 @@
     /** Current rotation of the display */
     val currentRotation: StateFlow<DisplayRotation>
 
+    /** Display change event indicating a change to the given displayId has occurred. */
+    val displayChanges: Flow<Int>
+
     /** Called on configuration changes, used to keep the display state in sync */
     fun onConfigurationChanged(newConfig: Configuration)
 }
@@ -74,6 +77,8 @@
         screenSizeFoldProvider = foldProvider
     }
 
+    override val displayChanges = displayRepository.displayChangeEvent
+
     override val isFolded: Flow<Boolean> =
         conflatedCallbackFlow {
                 val sendFoldStateUpdate = { state: Boolean ->
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
deleted file mode 100644
index 75ae061..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
+++ /dev/null
@@ -1,62 +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.biometrics.domain.interactor
-
-import android.hardware.biometrics.SensorLocationInternal
-import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.combine
-
-/** Business logic for SideFps overlay offsets. */
-interface SideFpsOverlayInteractor {
-
-    /** The displayId of the current display. */
-    val displayId: Flow<String>
-
-    /** Overlay offsets corresponding to given displayId. */
-    val overlayOffsets: Flow<SensorLocationInternal>
-
-    /** Called on display changes, used to keep the display state in sync */
-    fun onDisplayChanged(displayId: String)
-}
-
-@SysUISingleton
-class SideFpsOverlayInteractorImpl
-@Inject
-constructor(fingerprintPropertyRepository: FingerprintPropertyRepository) :
-    SideFpsOverlayInteractor {
-
-    private val _displayId: MutableStateFlow<String> = MutableStateFlow("")
-    override val displayId: Flow<String> = _displayId.asStateFlow()
-
-    override val overlayOffsets: Flow<SensorLocationInternal> =
-        combine(displayId, fingerprintPropertyRepository.sensorLocations) { displayId, offsets ->
-            offsets[displayId] ?: SensorLocationInternal.DEFAULT
-        }
-
-    override fun onDisplayChanged(displayId: String) {
-        _displayId.value = displayId
-    }
-
-    companion object {
-        private const val TAG = "SideFpsOverlayInteractorImpl"
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
new file mode 100644
index 0000000..f85203e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
@@ -0,0 +1,136 @@
+/*
+ * 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.biometrics.domain.interactor
+
+import android.content.Context
+import android.hardware.biometrics.SensorLocationInternal
+import android.view.WindowManager
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.model.SideFpsSensorLocation
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.isDefaultOrientation
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class SideFpsSensorInteractor
+@Inject
+constructor(
+    private val context: Context,
+    fingerprintPropertyRepository: FingerprintPropertyRepository,
+    windowManager: WindowManager,
+    displayStateInteractor: DisplayStateInteractor,
+    featureFlags: FeatureFlagsClassic,
+) {
+
+    private val sensorForCurrentDisplay =
+        combine(
+                displayStateInteractor.displayChanges,
+                fingerprintPropertyRepository.sensorLocations,
+                ::Pair
+            )
+            .map { (_, locations) -> locations[context.display?.uniqueId] }
+            .filterNotNull()
+
+    val isAvailable: Flow<Boolean> =
+        fingerprintPropertyRepository.sensorType.map { it == FingerprintSensorType.POWER_BUTTON }
+
+    val authenticationDuration: Flow<Long> =
+        flowOf(context.resources?.getInteger(R.integer.config_restToUnlockDuration)?.toLong() ?: 0L)
+
+    val isProlongedTouchRequiredForAuthentication: Flow<Boolean> =
+        isAvailable.flatMapLatest { sfpsAvailable ->
+            if (sfpsAvailable) {
+                // todo (b/305236201) also add the settings check here.
+                flowOf(featureFlags.isEnabled(Flags.REST_TO_UNLOCK))
+            } else {
+                flowOf(false)
+            }
+        }
+
+    val sensorLocation: Flow<SideFpsSensorLocation> =
+        combine(displayStateInteractor.currentRotation, sensorForCurrentDisplay, ::Pair).map {
+            (rotation, sensorLocation: SensorLocationInternal) ->
+            val isSensorVerticalInDefaultOrientation = sensorLocation.sensorLocationY != 0
+            // device dimensions in the current rotation
+            val size = windowManager.maximumWindowMetrics.bounds
+            val isDefaultOrientation = rotation.isDefaultOrientation()
+            // Width and height are flipped is device is not in rotation_0 or rotation_180
+            // Flipping it to the width and height of the device in default orientation.
+            val displayWidth = if (isDefaultOrientation) size.width() else size.height()
+            val displayHeight = if (isDefaultOrientation) size.height() else size.width()
+            val sensorWidth = context.resources?.getInteger(R.integer.config_sfpsSensorWidth) ?: 0
+
+            val (sensorLeft, sensorTop) =
+                if (isSensorVerticalInDefaultOrientation) {
+                    when (rotation) {
+                        DisplayRotation.ROTATION_0 -> {
+                            Pair(displayWidth, sensorLocation.sensorLocationY)
+                        }
+                        DisplayRotation.ROTATION_90 -> {
+                            Pair(sensorLocation.sensorLocationY, 0)
+                        }
+                        DisplayRotation.ROTATION_180 -> {
+                            Pair(0, displayHeight - sensorLocation.sensorLocationY - sensorWidth)
+                        }
+                        DisplayRotation.ROTATION_270 -> {
+                            Pair(
+                                displayHeight - sensorLocation.sensorLocationY - sensorWidth,
+                                displayWidth
+                            )
+                        }
+                    }
+                } else {
+                    when (rotation) {
+                        DisplayRotation.ROTATION_0 -> {
+                            Pair(sensorLocation.sensorLocationX, 0)
+                        }
+                        DisplayRotation.ROTATION_90 -> {
+                            Pair(0, displayWidth - sensorLocation.sensorLocationX - sensorWidth)
+                        }
+                        DisplayRotation.ROTATION_180 -> {
+                            Pair(
+                                displayWidth - sensorLocation.sensorLocationX - sensorWidth,
+                                displayHeight
+                            )
+                        }
+                        DisplayRotation.ROTATION_270 -> {
+                            Pair(displayHeight, sensorLocation.sensorLocationX)
+                        }
+                    }
+                }
+
+            SideFpsSensorLocation(
+                left = sensorLeft,
+                top = sensorTop,
+                width = sensorWidth,
+                isSensorVerticalInDefaultOrientation = isSensorVerticalInDefaultOrientation
+            )
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt
new file mode 100644
index 0000000..35f8e3b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.biometrics.domain.model
+
+data class SideFpsSensorLocation(
+    /** Pixel offset from the left of the screen */
+    val left: Int,
+    /** Pixel offset from the top of the screen */
+    val top: Int,
+    /** Width in pixels of the SFPS sensor */
+    val width: Int,
+    /**
+     * Whether the sensor is vertical when the device is in its default orientation (Rotation_0 or
+     * Rotation_180)
+     */
+    val isSensorVerticalInDefaultOrientation: Boolean
+)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt
index 10a3e91..336404c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt
@@ -10,6 +10,9 @@
     ROTATION_270,
 }
 
+fun DisplayRotation.isDefaultOrientation() =
+    this == DisplayRotation.ROTATION_0 || this == DisplayRotation.ROTATION_180
+
 /** Converts [Surface.Rotation] to corresponding [DisplayRotation] */
 fun Int.toDisplayRotation(): DisplayRotation =
     when (this) {
@@ -19,3 +22,12 @@
         Surface.ROTATION_270 -> DisplayRotation.ROTATION_270
         else -> throw IllegalArgumentException("Invalid DisplayRotation value: $this")
     }
+
+/** Converts [DisplayRotation] to corresponding [Surface.Rotation] */
+fun DisplayRotation.toRotation(): Int =
+    when (this) {
+        DisplayRotation.ROTATION_0 -> Surface.ROTATION_0
+        DisplayRotation.ROTATION_90 -> Surface.ROTATION_90
+        DisplayRotation.ROTATION_180 -> Surface.ROTATION_180
+        DisplayRotation.ROTATION_270 -> Surface.ROTATION_270
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 04a9cae..d57f31f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -67,6 +67,7 @@
 import android.media.AudioManager;
 import android.media.IAudioService;
 import android.media.MediaRouter2Manager;
+import android.media.projection.IMediaProjectionManager;
 import android.media.projection.MediaProjectionManager;
 import android.media.session.MediaSessionManager;
 import android.net.ConnectivityManager;
@@ -414,6 +415,13 @@
     }
 
     @Provides
+    @Singleton
+    static IMediaProjectionManager provideIMediaProjectionManager() {
+        return IMediaProjectionManager.Stub.asInterface(
+                ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE));
+    }
+
+    @Provides
     static MediaRouter2Manager provideMediaRouter2Manager(Context context) {
         return MediaRouter2Manager.getInstance(context);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
index 7510cf6c..d19efbd 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -15,14 +15,12 @@
  */
 package com.android.systemui.display.ui.view
 
-import android.app.Dialog
 import android.content.Context
 import android.os.Bundle
-import android.view.Gravity
 import android.view.View
-import android.view.WindowManager
 import android.widget.TextView
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog
 
 /**
  * Dialog used to decide what to do with a connected display.
@@ -35,7 +33,7 @@
     private val onStartMirroringClickListener: View.OnClickListener,
     private val onCancelMirroring: View.OnClickListener,
     theme: Int = R.style.Theme_SystemUI_Dialog,
-) : Dialog(context, theme) {
+) : SystemUIBottomSheetDialog(context, theme) {
 
     private lateinit var mirrorButton: TextView
     private lateinit var dismissButton: TextView
@@ -43,13 +41,8 @@
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        window?.apply {
-            setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
-            addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
-            setGravity(Gravity.BOTTOM)
-        }
         setContentView(R.layout.connected_display_dialog)
-        setCanceledOnTouchOutside(true)
+
         mirrorButton =
             requireViewById<TextView>(R.id.enable_display).apply {
                 setOnClickListener(onStartMirroringClickListener)
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
index 6946950..1c2ff4b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
@@ -36,6 +36,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.systemui.FeatureFlags;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.util.settings.GlobalSettings;
@@ -80,6 +81,7 @@
     private final Map<String, Boolean> mBooleanFlagCache = new ConcurrentHashMap<>();
     private final Map<String, String> mStringFlagCache = new ConcurrentHashMap<>();
     private final Map<String, Integer> mIntFlagCache = new ConcurrentHashMap<>();
+    private final FeatureFlags mGantryFlags;
     private final Restarter mRestarter;
 
     private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
@@ -124,6 +126,7 @@
             @Main Resources resources,
             ServerFlagReader serverFlagReader,
             @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags,
+            FeatureFlags gantryFlags,
             Restarter restarter) {
         mFlagManager = flagManager;
         mContext = context;
@@ -132,6 +135,7 @@
         mSystemProperties = systemProperties;
         mServerFlagReader = serverFlagReader;
         mAllFlags = allFlags;
+        mGantryFlags = gantryFlags;
         mRestarter = restarter;
     }
 
@@ -259,9 +263,8 @@
         if (!hasServerOverride
                 && !defaultValue
                 && result == null
-                && !flag.getName().equals(Flags.TEAMFOOD.getName())
                 && flag.getTeamfood()) {
-            return isEnabled(Flags.TEAMFOOD);
+            return mGantryFlags.sysuiTeamfood();
         }
 
         return result == null ? mServerFlagReader.readServerOverride(
@@ -534,7 +537,7 @@
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("can override: true");
-
+        pw.println("teamfood: " + mGantryFlags.sysuiTeamfood());
         pw.println("booleans: " + mBooleanFlagCache.size());
         // Sort our flags for dumping
         TreeMap<String, Boolean> dumpBooleanMap = new TreeMap<>(mBooleanFlagCache);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 11ac39f..b7c3662 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -36,7 +36,10 @@
  * See [FeatureFlagsClassicDebug] for instructions on flipping the flags via adb.
  */
 object Flags {
-    @JvmField val TEAMFOOD = unreleasedFlag("teamfood")
+    // IGNORE ME!
+    // Because flags are static, we need an ever-present flag to reference in some of the internal
+    // code that ensure that other flags are referenced and available.
+    @JvmField val NULL_FLAG = unreleasedFlag("null_flag")
 
     // 100 - notification
     // TODO(b/297792660): Tracking Bug
@@ -401,6 +404,9 @@
     @JvmField val SIGNAL_CALLBACK_DEPRECATION =
         unreleasedFlag("signal_callback_deprecation", teamfood = true)
 
+    // TODO(b/301610137): Tracking bug
+    @JvmField val NEW_NETWORK_SLICE_UI = unreleasedFlag("new_network_slice_ui", teamfood = true)
+
     // TODO(b/265892345): Tracking Bug
     val PLUG_IN_STATUS_BAR_CHIP = releasedFlag("plug_in_status_bar_chip")
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index fde92b8..0bac40b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -451,7 +451,8 @@
         if (!keyguardStateController.isKeyguardGoingAway &&
                 willUnlockWithInWindowLauncherAnimations) {
             try {
-                launcherUnlockController?.setUnlockAmount(1f, true /* forceIfAnimating */)
+                launcherUnlockController?.setUnlockAmount(1f,
+                        biometricUnlockControllerLazy.get().isWakeAndUnlock /* forceIfAnimating */)
             } catch (e: DeadObjectException) {
                 Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null in " +
                         "onKeyguardGoingAwayChanged(). Catching exception as this should mean " +
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index a1291a4..724241d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.os.SystemProperties
 import android.util.Log
+import com.android.internal.annotations.KeepForWeakReference
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.dagger.qualifiers.Main
@@ -82,6 +83,8 @@
     private var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
     private var reactivatedKey: String? = null
 
+    // Ensure the field (and associated reference) isn't removed during optimization.
+    @KeepForWeakReference
     private val userTrackerCallback =
         object : UserTracker.Callback {
             override fun onUserChanged(newUser: Int, userContext: Context) {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
new file mode 100644
index 0000000..8634b09
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.mediaprojection
+
+import android.media.projection.IMediaProjectionManager
+import android.os.Process
+import android.os.RemoteException
+import android.util.Log
+import com.android.internal.util.FrameworkStatsLog
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * Helper class for requesting that the server emit logs describing the MediaProjection setup
+ * experience.
+ */
+@SysUISingleton
+class MediaProjectionMetricsLogger
+@Inject
+constructor(private val service: IMediaProjectionManager) {
+    /**
+     * Request to log that the permission was requested.
+     *
+     * @param sessionCreationSource The entry point requesting permission to capture.
+     */
+    fun notifyPermissionProgress(state: Int, sessionCreationSource: Int) {
+        // TODO check that state & SessionCreationSource matches expected values
+        notifyToServer(state, sessionCreationSource)
+    }
+
+    /**
+     * Request to log that the permission request moved to the given state.
+     *
+     * Should not be used for the initialization state, since that
+     */
+    fun notifyPermissionProgress(state: Int) {
+        // TODO validate state is valid
+        notifyToServer(
+            state,
+            FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN)
+    }
+
+    /**
+     * Notifies system server that we are handling a particular state during the consent flow.
+     *
+     * Only used for emitting atoms.
+     *
+     * @param state The state that SystemUI is handling during the consent flow. Must be a valid
+     *   state defined in the MediaProjectionState enum.
+     * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED.
+     *   Indicates the entry point for requesting the permission. Must be a valid state defined in
+     *   the SessionCreationSource enum.
+     */
+    private fun notifyToServer(state: Int, sessionCreationSource: Int) {
+        Log.v(TAG, "FOO notifyToServer of state $state and source $sessionCreationSource")
+        try {
+            service.notifyPermissionRequestStateChange(
+                Process.myUid(), state, sessionCreationSource)
+        } catch (e: RemoteException) {
+            Log.e(
+                TAG,
+                "Error notifying server of permission flow state $state from source $sessionCreationSource",
+                e)
+        }
+    }
+
+    companion object {
+        const val TAG = "MediaProjectionMetricsLogger"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
index 2f10ad3..b9bafd4 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
@@ -89,15 +89,15 @@
                 }
             return listOf(
                 ScreenShareOption(
-                    mode = ENTIRE_SCREEN,
-                    spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
-                    warningText = entireScreenWarningText
-                ),
-                ScreenShareOption(
                     mode = SINGLE_APP,
                     spinnerText = R.string.screen_share_permission_dialog_option_single_app,
                     warningText = singleAppWarningText,
                     spinnerDisabledText = singleAppDisabledText,
+                ),
+                ScreenShareOption(
+                    mode = ENTIRE_SCREEN,
+                    spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
+                    warningText = entireScreenWarningText
                 )
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
index 37e8d9f..9bd5783 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
@@ -23,8 +23,8 @@
 @IntDef(ENTIRE_SCREEN, SINGLE_APP)
 annotation class ScreenShareMode
 
-const val ENTIRE_SCREEN = 0
-const val SINGLE_APP = 1
+const val SINGLE_APP = 0
+const val ENTIRE_SCREEN = 1
 
 class ScreenShareOption(
     @ScreenShareMode val mode: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
index d949a2a..67d390d 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.util.DeviceConfigProxy
 import com.android.systemui.util.asIndenting
+import com.android.systemui.util.annotations.WeaklyReferencedCallback
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.withIncreasedIndent
 import java.io.PrintWriter
@@ -144,6 +145,7 @@
         ipw.flush()
     }
 
+    @WeaklyReferencedCallback
     interface Callback {
         fun onFlagMicCameraChanged(flag: Boolean) {}
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 5c6e902..a698208 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -38,7 +38,6 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.DialogCuj;
 import com.android.systemui.animation.DialogLaunchAnimator;
@@ -53,12 +52,13 @@
 import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.statusbar.connectivity.WifiIndicators;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor;
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
+import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel;
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
 import com.android.systemui.statusbar.policy.HotspotController;
@@ -85,10 +85,9 @@
     private final NetworkController mNetworkController;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
     private final Callback mCallback = new Callback();
-    private final WifiInteractor mWifiInteractor;
     private final TileJavaAdapter mJavaAdapter;
     private final FeatureFlags mFeatureFlags;
-    private boolean mWifiConnected;
+    private boolean mCastTransportAllowed;
     private boolean mHotspotConnected;
 
     @Inject
@@ -107,7 +106,7 @@
             NetworkController networkController,
             HotspotController hotspotController,
             DialogLaunchAnimator dialogLaunchAnimator,
-            WifiInteractor wifiInteractor,
+            ConnectivityRepository connectivityRepository,
             TileJavaAdapter javaAdapter,
             FeatureFlags featureFlags
     ) {
@@ -117,7 +116,6 @@
         mKeyguard = keyguardStateController;
         mNetworkController = networkController;
         mDialogLaunchAnimator = dialogLaunchAnimator;
-        mWifiInteractor = wifiInteractor;
         mJavaAdapter = javaAdapter;
         mFeatureFlags = featureFlags;
         mController.observe(this, mCallback);
@@ -125,7 +123,11 @@
         if (!mFeatureFlags.isEnabled(SIGNAL_CALLBACK_DEPRECATION)) {
             mNetworkController.observe(this, mSignalCallback);
         } else {
-            mJavaAdapter.bind(this, mWifiInteractor.getWifiNetwork(), mNetworkModelConsumer);
+            mJavaAdapter.bind(
+                    this,
+                    connectivityRepository.getDefaultConnections(),
+                    mNetworkModelConsumer
+            );
         }
         hotspotController.observe(this, mHotspotCallback);
     }
@@ -282,7 +284,7 @@
         }
         state.icon = ResourceIcon.get(state.value ? R.drawable.ic_cast_connected
                 : R.drawable.ic_cast);
-        if (canCastToWifi() || state.value) {
+        if (canCastToNetwork() || state.value) {
             state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
             if (!state.value) {
                 state.secondaryLabel = "";
@@ -291,7 +293,7 @@
             state.forceExpandIcon = willPopDialog();
         } else {
             state.state = Tile.STATE_UNAVAILABLE;
-            String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi);
+            String noWifi = mContext.getString(R.string.quick_settings_cast_no_network);
             state.secondaryLabel = noWifi;
             state.forceExpandIcon = false;
         }
@@ -308,13 +310,13 @@
                 : mContext.getString(R.string.quick_settings_cast_device_default_name);
     }
 
-    private boolean canCastToWifi() {
-        return mWifiConnected || mHotspotConnected;
+    private boolean canCastToNetwork() {
+        return mCastTransportAllowed || mHotspotConnected;
     }
 
-    private void setWifiConnected(boolean connected) {
-        if (connected != mWifiConnected) {
-            mWifiConnected = connected;
+    private void setCastTransportAllowed(boolean connected) {
+        if (connected != mCastTransportAllowed) {
+            mCastTransportAllowed = connected;
             // Hotspot is not connected, so changes here should update
             if (!mHotspotConnected) {
                 refreshState();
@@ -326,14 +328,17 @@
         if (connected != mHotspotConnected) {
             mHotspotConnected = connected;
             // Wifi is not connected, so changes here should update
-            if (!mWifiConnected) {
+            if (!mCastTransportAllowed) {
                 refreshState();
             }
         }
     }
 
-    private final Consumer<WifiNetworkModel> mNetworkModelConsumer = (model) -> {
-        setWifiConnected(model instanceof WifiNetworkModel.Active);
+    private final Consumer<DefaultConnectionModel> mNetworkModelConsumer = (model) -> {
+        boolean isWifiDefault = model.getWifi().isDefault();
+        boolean isEthernetDefault = model.getEthernet().isDefault();
+        boolean hasCellularTransport = model.getMobile().isDefault();
+        setCastTransportAllowed((isWifiDefault || isEthernetDefault) && !hasCellularTransport);
     };
 
     private final SignalCallback mSignalCallback = new SignalCallback() {
@@ -342,7 +347,7 @@
                     // statusIcon.visible has the connected status information
                     boolean enabledAndConnected = indicators.enabled
                             && (indicators.qsIcon != null && indicators.qsIcon.visible);
-                    setWifiConnected(enabledAndConnected);
+                    setCastTransportAllowed(enabledAndConnected);
                 }
             };
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index a1d5d98..ade93b1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -186,14 +186,14 @@
         private fun createOptionList(): List<ScreenShareOption> {
             return listOf(
                 ScreenShareOption(
-                    ENTIRE_SCREEN,
-                    R.string.screen_share_permission_dialog_option_entire_screen,
-                    R.string.screenrecord_permission_dialog_warning_entire_screen
-                ),
-                ScreenShareOption(
                     SINGLE_APP,
                     R.string.screen_share_permission_dialog_option_single_app,
                     R.string.screenrecord_permission_dialog_warning_single_app
+                ),
+                ScreenShareOption(
+                    ENTIRE_SCREEN,
+                    R.string.screen_share_permission_dialog_option_entire_screen,
+                    R.string.screenrecord_permission_dialog_warning_entire_screen
                 )
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 4b3bd0b..127a57e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -303,6 +303,13 @@
     private String mPackageName = "";
     private BroadcastReceiver mCopyBroadcastReceiver;
 
+    // When false, the screenshot is taken without showing the ui. Note that this only applies to
+    // external displays, as on the default one the UI should **always** be shown.
+    // This is needed in case of screenshot during display mirroring, as adding another window to
+    // the external display makes mirroring stop.
+    // When there is a way to distinguish between displays that are mirroring or extending, this
+    // can be removed and we can directly show the ui only in the extended case.
+    private final Boolean mShowUIOnExternalDisplay;
     /** Tracks config changes that require re-creating UI */
     private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
             ActivityInfo.CONFIG_ORIENTATION
@@ -335,7 +342,8 @@
             AssistContentRequester assistContentRequester,
             MessageContainerController messageContainerController,
             Provider<ScreenshotSoundController> screenshotSoundController,
-            @Assisted int displayId
+            @Assisted int displayId,
+            @Assisted boolean showUIOnExternalDisplay
     ) {
         mScreenshotSmartActions = screenshotSmartActions;
         mNotificationsController = screenshotNotificationsController;
@@ -401,6 +409,7 @@
         mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
                         ClipboardOverlayController.COPY_OVERLAY_ACTION),
                 ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED);
+        mShowUIOnExternalDisplay = showUIOnExternalDisplay;
     }
 
     void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher,
@@ -448,6 +457,23 @@
 
         prepareViewForNewScreenshot(screenshot, oldPackageName);
 
+        if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) {
+            mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
+                    new AssistContentRequester.Callback() {
+                        @Override
+                        public void onAssistContentAvailable(AssistContent assistContent) {
+                            screenshot.setContextUrl(assistContent.getWebUri());
+                        }
+                    });
+        }
+
+        if (!shouldShowUi()) {
+            saveScreenshotInWorkerThread(
+                screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady,
+                (ignored) -> {});
+            return;
+        }
+
         saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
                 this::showUiOnActionsReady, this::showUiOnQuickShareActionReady);
 
@@ -482,22 +508,16 @@
                 screenshot.getUserHandle()));
         mScreenshotView.setScreenshot(screenshot);
 
-        if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) {
-            mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
-                    new AssistContentRequester.Callback() {
-                        @Override
-                        public void onAssistContentAvailable(AssistContent assistContent) {
-                            screenshot.setContextUrl(assistContent.getWebUri());
-                        }
-                    });
-        }
-
         // ignore system bar insets for the purpose of window layout
         mWindow.getDecorView().setOnApplyWindowInsetsListener(
                 (v, insets) -> WindowInsets.CONSUMED);
         mScreenshotHandler.cancelTimeout(); // restarted after animation
     }
 
+    private boolean shouldShowUi() {
+        return mDisplayId == Display.DEFAULT_DISPLAY || mShowUIOnExternalDisplay;
+    }
+
     void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) {
         withWindowAttached(() -> {
             if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
@@ -1199,7 +1219,13 @@
     /** Injectable factory to create screenshot controller instances for a specific display. */
     @AssistedFactory
     public interface Factory {
-        /** Creates an instance of the controller for that specific displayId. */
-        ScreenshotController create(int displayId);
+        /**
+         * Creates an instance of the controller for that specific displayId.
+         *
+         * @param displayId:               display to capture
+         * @param showUIOnExternalDisplay: Whether the UI should be shown if this is an external
+         *                                 display.
+         */
+        ScreenshotController create(int displayId, boolean showUIOnExternalDisplay);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 03c3f7a..abe40ff 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -135,7 +135,9 @@
     }
 
     private fun getScreenshotController(id: Int): ScreenshotController {
-        return screenshotControllers.computeIfAbsent(id) { screenshotControllerFactory.create(id) }
+        return screenshotControllers.computeIfAbsent(id) {
+            screenshotControllerFactory.create(id, /* showUIOnExternalDisplay= */ false)
+        }
     }
 
     /** For java compatibility only. see [executeScreenshots] */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 0be2265..75d52cb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -132,7 +132,8 @@
         if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
             mScreenshot = null;
         } else {
-            mScreenshot = screenshotControllerFactory.create(Display.DEFAULT_DISPLAY);
+            mScreenshot = screenshotControllerFactory.create(
+                    Display.DEFAULT_DISPLAY, /* showUIOnExternalDisplay= */ false);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index bd592c9..cf1fbe3 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.settings
 
+import com.android.systemui.util.annotations.WeaklyReferencedCallback
+
 import android.content.Context
 import android.content.pm.UserInfo
 import android.os.UserHandle
@@ -64,6 +66,7 @@
     /**
      * Callback for notifying of changes.
      */
+    @WeaklyReferencedCallback
     interface Callback {
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index ba0cf08..51972c2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -414,9 +414,7 @@
     private int mDisplayLeftInset = 0; // in pixels
 
     @VisibleForTesting
-    KeyguardClockPositionAlgorithm
-            mClockPositionAlgorithm =
-            new KeyguardClockPositionAlgorithm();
+    KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
     private final KeyguardClockPositionAlgorithm.Result
             mClockPositionResult =
             new KeyguardClockPositionAlgorithm.Result();
@@ -779,7 +777,8 @@
             KeyguardViewConfigurator keyguardViewConfigurator,
             KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
             SplitShadeStateController splitShadeStateController,
-            PowerInteractor powerInteractor) {
+            PowerInteractor powerInteractor,
+            KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm) {
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onKeyguardFadingAwayChanged() {
@@ -807,6 +806,7 @@
         mKeyguardInteractor = keyguardInteractor;
         mPowerInteractor = powerInteractor;
         mKeyguardViewConfigurator = keyguardViewConfigurator;
+        mClockPositionAlgorithm = keyguardClockPositionAlgorithm;
         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 25bd8e7..cb95b25 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -38,7 +38,6 @@
 import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
 import com.android.systemui.Dumpable
-import com.android.systemui.res.R
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
@@ -49,6 +48,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.ChipVisibilityListener
 import com.android.systemui.qs.HeaderPrivacyIconsController
+import com.android.systemui.res.R
 import com.android.systemui.shade.ShadeHeaderController.Companion.HEADER_TRANSITION_ID
 import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
 import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
@@ -304,7 +304,8 @@
 
         iconManager = tintedIconManagerFactory.create(iconContainer, StatusBarLocation.QS)
         iconManager.setTint(
-            Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
+            Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary),
+            Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimaryInverse),
         )
 
         carrierIconSlots =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 2147510..f32f1c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -16,9 +16,16 @@
 package com.android.systemui.statusbar;
 
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.os.UserHandle.USER_NULL;
+import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
+import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
 import android.app.ActivityOptions;
 import android.app.KeyguardManager;
 import android.app.Notification;
@@ -30,8 +37,10 @@
 import android.content.IntentSender;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerExecutor;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -41,14 +50,18 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
 
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.recents.OverviewProxyService;
@@ -63,7 +76,9 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -84,6 +99,11 @@
     private final SecureSettings mSecureSettings;
     private final Object mLock = new Object();
 
+    private static final Uri SHOW_LOCKSCREEN =
+            Settings.Secure.getUriFor(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+    private static final Uri SHOW_PRIVATE_LOCKSCREEN =
+            Settings.Secure.getUriFor(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
     private final Lazy<NotificationVisibilityProvider> mVisibilityProviderLazy;
     private final Lazy<CommonNotifCollection> mCommonNotifCollectionLazy;
     private final DevicePolicyManager mDevicePolicyManager;
@@ -91,6 +111,23 @@
     private final SparseBooleanArray mUsersWithSeparateWorkChallenge = new SparseBooleanArray();
     private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
     private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
+
+    // The variables between mUsersDpcAllowingNotifications and
+    // mUsersUsersAllowingPrivateNotifications (inclusive) are written on a background thread
+    // and read on the main thread. Because the pipeline needs these values, adding locks would
+    // introduce too much jank. This means that some pipeline runs could get incorrect values, that
+    // would be fixed on the next pipeline run. We think this will be rare since a pipeline run
+    // would have to overlap with a DPM sync or a user changing a value in Settings, and we run the
+    // pipeline frequently enough that it should be corrected by the next time it matters for the
+    // user.
+    private final SparseBooleanArray mUsersDpcAllowingNotifications = new SparseBooleanArray();
+    private final SparseBooleanArray mUsersUsersAllowingNotifications = new SparseBooleanArray();
+    private boolean mKeyguardAllowingNotifications = true;
+    private final SparseBooleanArray mUsersDpcAllowingPrivateNotifications
+            = new SparseBooleanArray();
+    private final SparseBooleanArray mUsersUsersAllowingPrivateNotifications
+            = new SparseBooleanArray();
+
     private final SparseBooleanArray mUsersInLockdownLatestResult = new SparseBooleanArray();
     private final SparseBooleanArray mShouldHideNotifsLatestResult = new SparseBooleanArray();
     private final UserManager mUserManager;
@@ -99,24 +136,39 @@
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final NotificationClickNotifier mClickNotifier;
     private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy;
+    private final FeatureFlagsClassic mFeatureFlags;
     private boolean mShowLockscreenNotifications;
     private LockPatternUtils mLockPatternUtils;
     protected KeyguardManager mKeyguardManager;
     private int mState = StatusBarState.SHADE;
     private final ListenerSet<NotificationStateChangedListener> mNotifStateChangedListeners =
             new ListenerSet<>();
+    private final Collection<Uri> mLockScreenUris = new ArrayList<>();
+
 
     protected final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
 
-            if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) &&
-                    isCurrentProfile(getSendingUserId())) {
-                mUsersAllowingPrivateNotifications.clear();
-                updateLockscreenNotificationSetting();
-                // TODO(b/231976036): Consolidate pipeline invalidations related to this event
-                // notifyNotificationStateChanged();
+            if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) {
+                if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+                    boolean changed = updateDpcSettings(getSendingUserId());
+                    if (mCurrentUserId == getSendingUserId()) {
+                        changed |= updateLockscreenNotificationSetting();
+                    }
+                    if (changed) {
+                        notifyNotificationStateChanged();
+                    }
+                } else {
+                    if (isCurrentProfile(getSendingUserId())) {
+                        mUsersAllowingPrivateNotifications.clear();
+                        updateLockscreenNotificationSetting();
+                        // TODO(b/231976036): Consolidate pipeline invalidations related to this
+                        //  event
+                        // notifyNotificationStateChanged();
+                    }
+                }
             }
         }
     };
@@ -136,6 +188,14 @@
                     updateCurrentProfilesCache();
                     break;
                 case Intent.ACTION_USER_ADDED:
+                    updateCurrentProfilesCache();
+                    if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+                        final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+                        mBackgroundHandler.post(() -> {
+                            initValuesForUser(userId);
+                        });
+                    }
+                    break;
                 case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
                 case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
                     updateCurrentProfilesCache();
@@ -193,6 +253,8 @@
 
     protected final Context mContext;
     private final Handler mMainHandler;
+    private final Handler mBackgroundHandler;
+    private final Executor mBackgroundExecutor;
     protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
     protected final SparseArray<UserInfo> mCurrentManagedProfiles = new SparseArray<>();
 
@@ -214,13 +276,18 @@
             KeyguardManager keyguardManager,
             StatusBarStateController statusBarStateController,
             @Main Handler mainHandler,
+            @Background Handler backgroundHandler,
+            @Background Executor backgroundExecutor,
             DeviceProvisionedController deviceProvisionedController,
             KeyguardStateController keyguardStateController,
             SecureSettings secureSettings,
             DumpManager dumpManager,
-            LockPatternUtils lockPatternUtils) {
+            LockPatternUtils lockPatternUtils,
+            FeatureFlagsClassic featureFlags) {
         mContext = context;
         mMainHandler = mainHandler;
+        mBackgroundHandler = backgroundHandler;
+        mBackgroundExecutor = backgroundExecutor;
         mDevicePolicyManager = devicePolicyManager;
         mUserManager = userManager;
         mUserTracker = userTracker;
@@ -236,6 +303,10 @@
         mDeviceProvisionedController = deviceProvisionedController;
         mSecureSettings = secureSettings;
         mKeyguardStateController = keyguardStateController;
+        mFeatureFlags = featureFlags;
+
+        mLockScreenUris.add(SHOW_LOCKSCREEN);
+        mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN);
 
         dumpManager.registerDumpable(this);
     }
@@ -243,16 +314,53 @@
     public void setUpWithPresenter(NotificationPresenter presenter) {
         mPresenter = presenter;
 
-        mLockscreenSettingsObserver = new ContentObserver(mMainHandler) {
+        mLockscreenSettingsObserver = new ContentObserver(
+                mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
+                        ? mBackgroundHandler
+                        : mMainHandler) {
+
             @Override
-            public void onChange(boolean selfChange) {
-                // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
-                // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
-                mUsersAllowingPrivateNotifications.clear();
-                mUsersAllowingNotifications.clear();
-                // ... and refresh all the notifications
-                updateLockscreenNotificationSetting();
-                notifyNotificationStateChanged();
+            public void onChange(boolean selfChange, Collection<Uri> uris, int flags) {
+                if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+                    @SuppressLint("MissingPermission")
+                    List<UserInfo> users = mUserManager.getUsers();
+                    for (int i = users.size() - 1; i >= 0; i--) {
+                        onChange(selfChange, uris, flags,users.get(i).getUserHandle());
+                    }
+                } else {
+                    // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
+                    // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
+                    mUsersAllowingPrivateNotifications.clear();
+                    mUsersAllowingNotifications.clear();
+                    // ... and refresh all the notifications
+                    updateLockscreenNotificationSetting();
+                    notifyNotificationStateChanged();
+                }
+            }
+
+            // Note: even though this is an override, this method is not called by the OS
+            // since we're not in system_server. We are using it internally for cases when
+            // we have a single user id available (e.g. from USER_ADDED).
+            @Override
+            public void onChange(boolean selfChange, Collection<Uri> uris,
+                    int flags, UserHandle user) {
+                if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+                    boolean changed = false;
+                    for (Uri uri: uris) {
+                        if (SHOW_LOCKSCREEN.equals(uri)) {
+                            changed |= updateUserShowSettings(user.getIdentifier());
+                        } else if (SHOW_PRIVATE_LOCKSCREEN.equals(uri)) {
+                            changed |= updateUserShowPrivateSettings(user.getIdentifier());
+                        }
+                    }
+
+                    if (mCurrentUserId == user.getIdentifier()) {
+                        changed |= updateLockscreenNotificationSetting();
+                    }
+                    if (changed) {
+                        notifyNotificationStateChanged();
+                    }
+                }
             }
         };
 
@@ -268,23 +376,26 @@
         };
 
         mContext.getContentResolver().registerContentObserver(
-                mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false,
+                SHOW_LOCKSCREEN, false,
                 mLockscreenSettingsObserver,
                 UserHandle.USER_ALL);
 
         mContext.getContentResolver().registerContentObserver(
-                mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
+                SHOW_PRIVATE_LOCKSCREEN,
                 true,
                 mLockscreenSettingsObserver,
                 UserHandle.USER_ALL);
 
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
-                mSettingsObserver);
+        if (!mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
+                    mSettingsObserver);
+        }
 
         mBroadcastDispatcher.registerReceiver(mAllUsersReceiver,
                 new IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
-                null /* handler */, UserHandle.ALL);
+                mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
+                        ? mBackgroundExecutor : null, UserHandle.ALL);
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_ADDED);
@@ -305,7 +416,23 @@
         mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late
         updateCurrentProfilesCache();
 
-        mSettingsObserver.onChange(false);  // set up
+        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            // Set  up
+            mBackgroundHandler.post(() -> {
+                @SuppressLint("MissingPermission") List<UserInfo> users = mUserManager.getUsers();
+                for (int i = users.size() - 1; i >= 0; i--) {
+                    initValuesForUser(users.get(i).id);
+                }
+            });
+        } else {
+            mSettingsObserver.onChange(false);  // set up
+        }
+    }
+
+    private void initValuesForUser(@UserIdInt int userId) {
+        mLockscreenSettingsObserver.onChange(
+                false, mLockScreenUris, 0, UserHandle.of(userId));
+        updateDpcSettings(userId);
     }
 
     public boolean shouldShowLockscreenNotifications() {
@@ -322,17 +449,75 @@
         mShowLockscreenNotifications = show;
     }
 
-    protected void updateLockscreenNotificationSetting() {
-        final boolean show = mSecureSettings.getIntForUser(
-                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
-                1,
-                mCurrentUserId) != 0;
-        final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
-                null /* admin */, mCurrentUserId);
-        final boolean allowedByDpm = (dpmFlags
-                & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+    protected boolean updateLockscreenNotificationSetting() {
+        boolean show;
+        boolean allowedByDpm;
 
+        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            show = mUsersUsersAllowingNotifications.get(mCurrentUserId)
+                    && mKeyguardAllowingNotifications;
+            // If DPC never notified us about a user, that means they have no policy for the user,
+            // and they allow the behavior
+            allowedByDpm = mUsersDpcAllowingNotifications.get(mCurrentUserId, true);
+        } else {
+            show = mSecureSettings.getIntForUser(
+                    LOCK_SCREEN_SHOW_NOTIFICATIONS,
+                    1,
+                    mCurrentUserId) != 0;
+            final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
+                    null /* admin */, mCurrentUserId);
+            allowedByDpm = (dpmFlags
+                    & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+        }
+
+        final boolean oldValue = mShowLockscreenNotifications;
         setShowLockscreenNotifications(show && allowedByDpm);
+
+        return oldValue != mShowLockscreenNotifications;
+    }
+
+    @WorkerThread
+    protected boolean updateDpcSettings(int userId) {
+        boolean originalAllowLockscreen = mUsersDpcAllowingNotifications.get(userId);
+        boolean originalAllowPrivate = mUsersDpcAllowingPrivateNotifications.get(userId);
+        final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
+                null /* admin */, userId);
+        final boolean allowedLockscreen = (dpmFlags & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+        final boolean allowedPrivate = (dpmFlags & KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0;
+        mUsersDpcAllowingNotifications.put(userId, allowedLockscreen);
+        mUsersDpcAllowingPrivateNotifications.put(userId, allowedPrivate);
+        return (originalAllowLockscreen != allowedLockscreen)
+                || (originalAllowPrivate != allowedPrivate);
+    }
+
+    @WorkerThread
+    private boolean updateUserShowSettings(int userId) {
+        boolean originalAllowLockscreen = mUsersUsersAllowingNotifications.get(userId);
+        boolean newAllowLockscreen = mSecureSettings.getIntForUser(
+                LOCK_SCREEN_SHOW_NOTIFICATIONS,
+                1,
+                userId) != 0;
+        mUsersUsersAllowingNotifications.put(userId, newAllowLockscreen);
+        boolean keyguardChanged = updateGlobalKeyguardSettings();
+        return (newAllowLockscreen != originalAllowLockscreen) || keyguardChanged;
+    }
+
+    @WorkerThread
+    private boolean updateUserShowPrivateSettings(int userId) {
+        boolean originalValue = mUsersUsersAllowingPrivateNotifications.get(userId);
+        boolean newValue = mSecureSettings.getIntForUser(
+                LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+                0,
+                userId) != 0;
+        mUsersUsersAllowingPrivateNotifications.put(userId, newValue);
+        return (newValue != originalValue);
+    }
+
+    @WorkerThread
+    private boolean updateGlobalKeyguardSettings() {
+        final boolean oldValue = mKeyguardAllowingNotifications;
+        mKeyguardAllowingNotifications = mKeyguardManager.getPrivateNotificationsAllowed();
+        return oldValue != mKeyguardAllowingNotifications;
     }
 
     /**
@@ -340,21 +525,41 @@
      * when the lockscreen is in "public" (secure & locked) mode?
      */
     public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
-        if (userHandle == UserHandle.USER_ALL) {
-            return true;
-        }
+        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            if (userHandle == UserHandle.USER_ALL) {
+                userHandle = mCurrentUserId;
+            }
+            if (mUsersUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+                // default value before moving to 'released'
+                Log.wtf(TAG, "Asking for redact notifs setting too early", new Throwable());
+                updateUserShowPrivateSettings(userHandle);
+            }
+            if (mUsersDpcAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+                // default value before moving to 'released'
+                Log.wtf(TAG, "Asking for redact notifs dpm override too early", new Throwable());
+                updateDpcSettings(userHandle);
+            }
+            return mUsersUsersAllowingPrivateNotifications.get(userHandle)
+                    && mUsersDpcAllowingPrivateNotifications.get(userHandle);
+        } else {
+            if (userHandle == UserHandle.USER_ALL) {
+                return true;
+            }
 
-        if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
-            final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
-                    Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
-            final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
-                    DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
-            final boolean allowed = allowedByUser && allowedByDpm;
-            mUsersAllowingPrivateNotifications.append(userHandle, allowed);
-            return allowed;
-        }
+            if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+                final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
+                        LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
+                final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
+                        KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+                final boolean allowed = allowedByUser && allowedByDpm;
+                mUsersAllowingPrivateNotifications.append(userHandle, allowed);
+                return allowed;
+            }
 
-        return mUsersAllowingPrivateNotifications.get(userHandle);
+            return mUsersAllowingPrivateNotifications.get(userHandle);
+        }
     }
 
     /**
@@ -406,21 +611,44 @@
      * "public" (secure & locked) mode?
      */
     public boolean userAllowsNotificationsInPublic(int userHandle) {
-        if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) {
-            return true;
-        }
+        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            // Unlike 'show private', settings does not show a copy of this setting for each
+            // profile, so it inherits from the parent user.
+            if (userHandle == UserHandle.USER_ALL || mCurrentManagedProfiles.contains(userHandle)) {
+                userHandle = mCurrentUserId;
+            }
+            if (mUsersUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
+                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+                // default value before moving to 'released'
+                Log.wtf(TAG, "Asking for show notifs setting too early", new Throwable());
+                updateUserShowSettings(userHandle);
+            }
+            if (mUsersDpcAllowingNotifications.indexOfKey(userHandle) < 0) {
+                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+                // default value before moving to 'released'
+                Log.wtf(TAG, "Asking for show notifs dpm override too early", new Throwable());
+                updateDpcSettings(userHandle);
+            }
+            return mUsersUsersAllowingNotifications.get(userHandle)
+                    && mUsersDpcAllowingNotifications.get(userHandle)
+                    && mKeyguardAllowingNotifications;
+        } else {
+            if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) {
+                return true;
+            }
 
-        if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
-            final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
-                    Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
-            final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
-                    DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
-            final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed();
-            final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem;
-            mUsersAllowingNotifications.append(userHandle, allowed);
-            return allowed;
+            if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
+                final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
+                        LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
+                final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
+                        KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+                final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed();
+                final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem;
+                mUsersAllowingNotifications.append(userHandle, allowed);
+                return allowed;
+            }
+            return mUsersAllowingNotifications.get(userHandle);
         }
-        return mUsersAllowingNotifications.get(userHandle);
     }
 
     /** @return true if the entry needs redaction when on the lockscreen. */
@@ -451,9 +679,15 @@
             return true;
         }
         NotificationEntry entry = mCommonNotifCollectionLazy.get().getEntry(key);
-        return entry != null
-                && entry.getRanking().getLockscreenVisibilityOverride() 
-                == Notification.VISIBILITY_PRIVATE;
+        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            return entry != null
+                    && entry.getRanking().getChannel().getLockscreenVisibility()
+                    == Notification.VISIBILITY_PRIVATE;
+        } else {
+            return entry != null
+                    && entry.getRanking().getLockscreenVisibilityOverride()
+                    == Notification.VISIBILITY_PRIVATE;
+        }
     }
 
     private void updateCurrentProfilesCache() {
@@ -491,20 +725,6 @@
     }
 
     /**
-     * If any managed/work profiles are in public mode.
-     */
-    public boolean isAnyManagedProfilePublicMode() {
-        synchronized (mLock) {
-            for (int i = mCurrentManagedProfiles.size() - 1; i >= 0; i--) {
-                if (isLockscreenPublicMode(mCurrentManagedProfiles.valueAt(i).id)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
      * Returns the current user id. This can change if the user is switched.
      */
     public int getCurrentUserId() {
@@ -581,8 +801,16 @@
     }
 
     private void notifyNotificationStateChanged() {
-        for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
-            listener.onNotificationStateChanged();
+        if (!Looper.getMainLooper().isCurrentThread()) {
+            mMainHandler.post(() -> {
+                for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
+                    listener.onNotificationStateChanged();
+                }
+            });
+        } else {
+            for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
+                listener.onNotificationStateChanged();
+            }
         }
     }
 
@@ -620,5 +848,15 @@
         pw.println(mUsersInLockdownLatestResult);
         pw.print("  mShouldHideNotifsLatestResult=");
         pw.println(mShouldHideNotifsLatestResult);
+        pw.print("  mUsersDpcAllowingNotifications=");
+        pw.println(mUsersDpcAllowingNotifications);
+        pw.print("  mUsersUsersAllowingNotifications=");
+        pw.println(mUsersUsersAllowingNotifications);
+        pw.print("  mKeyguardAllowingNotifications=");
+        pw.println(mKeyguardAllowingNotifications);
+        pw.print("  mUsersDpcAllowingPrivateNotifications=");
+        pw.println(mUsersDpcAllowingPrivateNotifications);
+        pw.print("  mUsersUsersAllowingPrivateNotifications=");
+        pw.println(mUsersUsersAllowingPrivateNotifications);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
index 1196211..595ab70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
@@ -21,6 +21,21 @@
 public interface StatusIconDisplayable extends DarkReceiver {
     String getSlot();
     void setStaticDrawableColor(int color);
+
+    /**
+     * For a layer drawable, or one that has a background, {@code tintColor} should be used as the
+     * background tint for the container, while {@code contrastColor} can be used as the foreground
+     * drawable's tint so that it is visible on the background. Essentially, tintColor should apply
+     * to the portion of the icon that borders the underlying window content (status bar's
+     * background), and the contrastColor only need be used to distinguish from the tintColor.
+     *
+     * Defaults to calling {@link #setStaticDrawableColor(int)} with only the tint color, so modern
+     * callers can just call this method and still get the default behavior.
+     */
+    default void setStaticDrawableColor(int tintColor, int contrastColor) {
+        setStaticDrawableColor(tintColor);
+    }
+
     void setDecorColor(int color);
 
     /** Sets the visible state that this displayable should be. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index b2c32cd..db7f46e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -1,6 +1,5 @@
 package com.android.systemui.statusbar.notification.interruption
 
-import android.app.Notification
 import android.app.Notification.VISIBILITY_SECRET
 import android.content.Context
 import android.database.ContentObserver
@@ -14,6 +13,8 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -78,7 +79,8 @@
     private val statusBarStateController: SysuiStatusBarStateController,
     private val userTracker: UserTracker,
     private val secureSettings: SecureSettings,
-    private val globalSettings: GlobalSettings
+    private val globalSettings: GlobalSettings,
+    private val featureFlags: FeatureFlagsClassic
 ) : CoreStartable, KeyguardNotificationVisibilityProvider {
     private val showSilentNotifsUri =
             secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)
@@ -201,7 +203,7 @@
             // device isn't public, no need to check public-related settings, so allow
             !lockscreenUserManager.isLockscreenPublicMode(user) -> false
             // entry is meant to be secret on the lockscreen, disallow
-            entry.ranking.lockscreenVisibilityOverride == Notification.VISIBILITY_SECRET -> true
+            isRankingVisibilitySecret(entry) -> true
             // disallow if user disallows notifications in public
             else -> !lockscreenUserManager.userAllowsNotificationsInPublic(user)
         }
@@ -215,6 +217,17 @@
         }
     }
 
+    private fun isRankingVisibilitySecret(entry: NotificationEntry): Boolean {
+        return if (featureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            // ranking.lockscreenVisibilityOverride contains possibly out of date DPC and Setting
+            // info, and NotificationLockscreenUserManagerImpl is already listening for updates
+            // to those
+            entry.ranking.channel.lockscreenVisibility == VISIBILITY_SECRET
+        } else {
+            entry.ranking.lockscreenVisibilityOverride == VISIBILITY_SECRET
+        }
+    }
+
     override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run {
         println("isLockedOrLocking=$isLockedOrLocking")
         withIncreasedIndent {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index 1576aa2..6f992ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -47,6 +47,11 @@
     private final ArrayMap<Object, DarkReceiver> mReceivers = new ArrayMap<>();
 
     private int mIconTint = DEFAULT_ICON_TINT;
+    private int mContrastTint = DEFAULT_INVERSE_ICON_TINT;
+
+    private int mDarkModeContrastColor = DEFAULT_ICON_TINT;
+    private int mLightModeContrastColor = DEFAULT_INVERSE_ICON_TINT;
+
     private float mDarkIntensity;
     private int mDarkModeIconColorSingleTone;
     private int mLightModeIconColorSingleTone;
@@ -83,6 +88,7 @@
     public void addDarkReceiver(DarkReceiver receiver) {
         mReceivers.put(receiver, receiver);
         receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
+        receiver.onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);
     }
 
     public void addDarkReceiver(ImageView imageView) {
@@ -90,6 +96,7 @@
                 ColorStateList.valueOf(getTint(mTintAreas, imageView, mIconTint)));
         mReceivers.put(imageView, receiver);
         receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
+        receiver.onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);
     }
 
     public void removeDarkReceiver(DarkReceiver object) {
@@ -102,6 +109,7 @@
 
     public void applyDark(DarkReceiver object) {
         mReceivers.get(object).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
+        mReceivers.get(object).onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);
     }
 
     /**
@@ -125,8 +133,13 @@
     @Override
     public void applyDarkIntensity(float darkIntensity) {
         mDarkIntensity = darkIntensity;
-        mIconTint = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
+        ArgbEvaluator evaluator = ArgbEvaluator.getInstance();
+
+        mIconTint = (int) evaluator.evaluate(darkIntensity,
                 mLightModeIconColorSingleTone, mDarkModeIconColorSingleTone);
+        mContrastTint = (int) evaluator
+                .evaluate(darkIntensity, mLightModeContrastColor, mDarkModeContrastColor);
+
         applyIconTint();
     }
 
@@ -139,6 +152,7 @@
         mDarkChangeFlow.setValue(new DarkChange(mTintAreas, mDarkIntensity, mIconTint));
         for (int i = 0; i < mReceivers.size(); i++) {
             mReceivers.valueAt(i).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
+            mReceivers.valueAt(i).onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);
         }
     }
 
@@ -146,6 +160,16 @@
     public void dump(PrintWriter pw, String[] args) {
         pw.println("DarkIconDispatcher: ");
         pw.println("  mIconTint: 0x" + Integer.toHexString(mIconTint));
+        pw.println("  mContrastTint: 0x" + Integer.toHexString(mContrastTint));
+
+        pw.println("  mDarkModeIconColorSingleTone: 0x"
+                + Integer.toHexString(mDarkModeIconColorSingleTone));
+        pw.println("  mLightModeIconColorSingleTone: 0x"
+                + Integer.toHexString(mLightModeIconColorSingleTone));
+
+        pw.println("  mDarkModeContrastColor: 0x" + Integer.toHexString(mDarkModeContrastColor));
+        pw.println("  mLightModeContrastColor: 0x" + Integer.toHexString(mLightModeContrastColor));
+
         pw.println("  mDarkIntensity: " + mDarkIntensity + "f");
         pw.println("  mTintAreas: " + mTintAreas);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index de9854a..5deb08a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -28,10 +28,10 @@
 import android.widget.LinearLayout;
 
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.res.R;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger;
@@ -54,6 +54,7 @@
     private ModernStatusBarWifiView mModernWifiView;
     private boolean mDemoMode;
     private int mColor;
+    private int mContrastColor;
 
     private final MobileIconsViewModel mMobileIconsViewModel;
     private final StatusBarLocation mLocation;
@@ -68,6 +69,7 @@
         mStatusIcons = statusIcons;
         mIconSize = iconSize;
         mColor = DarkIconDispatcher.DEFAULT_ICON_TINT;
+        mContrastColor = DarkIconDispatcher.DEFAULT_INVERSE_ICON_TINT;
         mMobileIconsViewModel = mobileIconsViewModel;
         mLocation = location;
 
@@ -89,15 +91,17 @@
         ((ViewGroup) getParent()).removeView(this);
     }
 
-    public void setColor(int color) {
+    /** Set the tint colors */
+    public void setColor(int color, int contrastColor) {
         mColor = color;
+        mContrastColor = contrastColor;
         updateColors();
     }
 
     private void updateColors() {
         for (int i = 0; i < getChildCount(); i++) {
             StatusIconDisplayable child = (StatusIconDisplayable) getChildAt(i);
-            child.setStaticDrawableColor(mColor);
+            child.setStaticDrawableColor(mColor, mContrastColor);
             child.setDecorColor(mColor);
         }
     }
@@ -223,7 +227,7 @@
         StatusBarIconView v = new StatusBarIconView(getContext(), slot, null, false);
         v.setTag(slot);
         v.set(icon);
-        v.setStaticDrawableColor(mColor);
+        v.setStaticDrawableColor(mColor, mContrastColor);
         v.setDecorColor(mColor);
         addView(v, 0, createLayoutParams());
     }
@@ -269,7 +273,7 @@
         }
 
         mModernWifiView = view;
-        mModernWifiView.setStaticDrawableColor(mColor);
+        mModernWifiView.setStaticDrawableColor(mColor, mContrastColor);
         addView(view, viewIndex, createLayoutParams());
     }
 
@@ -305,14 +309,20 @@
     }
 
     @Override
-    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
-        setColor(DarkIconDispatcher.getTint(areas, mStatusIcons, tint));
+    public void onDarkChangedWithContrast(ArrayList<Rect> areas, int tint, int contrastTint) {
+        setColor(tint, contrastTint);
 
         if (mModernWifiView != null) {
-            mModernWifiView.onDarkChanged(areas, darkIntensity, tint);
+            mModernWifiView.onDarkChangedWithContrast(areas, tint, contrastTint);
         }
+
         for (ModernStatusBarMobileView view : mModernMobileViews) {
-            view.onDarkChanged(areas, darkIntensity, tint);
+            view.onDarkChangedWithContrast(areas, tint, contrastTint);
         }
     }
+
+    @Override
+    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+        // not needed
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index fb5a530..0a03af7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -26,14 +26,20 @@
 import com.android.app.animation.Interpolators;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.keyguard.KeyguardStatusView;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.core.Logger;
+import com.android.systemui.log.dagger.KeyguardClockLog;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
 
+import javax.inject.Inject;
+
 /**
  * Utility class to calculate the clock position and top padding of notifications on Keyguard.
  */
 public class KeyguardClockPositionAlgorithm {
+    private static final String TAG = "KeyguardClockPositionAlgorithm";
 
     /**
      * Margin between the bottom of the status view and the notification shade.
@@ -147,6 +153,13 @@
      */
     private boolean mIsClockTopAligned;
 
+    private Logger mLogger;
+
+    @Inject
+    public KeyguardClockPositionAlgorithm(@KeyguardClockLog LogBuffer logBuffer) {
+        mLogger = new Logger(logBuffer, TAG);
+    }
+
     /**
      * Refreshes the dimension values.
      */
@@ -306,6 +319,20 @@
                 + fullyDarkBurnInOffset
                 + shift;
         mCurrentBurnInOffsetY = MathUtils.lerp(0, fullyDarkBurnInOffset, darkAmount);
+        final String inputs = "panelExpansion: " + panelExpansion + " darkAmount: " + darkAmount;
+        final String outputs = "clockY: " + clockY
+                + " burnInPreventionOffsetY: " + burnInPreventionOffsetY
+                + " fullyDarkBurnInOffset: " + fullyDarkBurnInOffset
+                + " shift: " + shift
+                + " mOverStretchAmount: " + mOverStretchAmount
+                + " mCurrentBurnInOffsetY: " + mCurrentBurnInOffsetY;
+        mLogger.i(msg -> {
+            return msg.getStr1() + " -> " + msg.getStr2();
+        }, msg -> {
+            msg.setStr1(inputs);
+            msg.setStr2(outputs);
+            return kotlin.Unit.INSTANCE;
+        });
         return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 58126ae..8a64a50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -436,10 +436,14 @@
     private void updateIconsAndTextColors(StatusBarIconController.TintedIconManager iconManager) {
         @ColorInt int textColor = Utils.getColorAttrDefaultColor(mContext,
                 R.attr.wallpaperTextColor);
+        float luminance = Color.luminance(textColor);
         @ColorInt int iconColor = Utils.getColorStateListDefaultColor(mContext,
-                Color.luminance(textColor) < 0.5
+                    luminance < 0.5
                         ? com.android.settingslib.R.color.dark_mode_icon_color_single_tone
                         : com.android.settingslib.R.color.light_mode_icon_color_single_tone);
+        @ColorInt int contrastColor = luminance < 0.5
+                ? DarkIconDispatcherImpl.DEFAULT_ICON_TINT
+                : DarkIconDispatcherImpl.DEFAULT_INVERSE_ICON_TINT;
         float intensity = textColor == Color.WHITE ? 0 : 1;
         mCarrierLabel.setTextColor(iconColor);
 
@@ -451,7 +455,7 @@
         }
 
         if (iconManager != null) {
-            iconManager.setTint(iconColor);
+            iconManager.setTint(iconColor, contrastColor);
         }
 
         mDarkChange.setValue(new DarkChange(mEmptyTintRect, intensity, iconColor));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index ffeb1a8..9ae4195 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -31,11 +31,11 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusIconDisplayable;
@@ -248,7 +248,10 @@
      *
      */
     class TintedIconManager extends IconManager {
+        // The main tint, used as the foreground in non layer drawables
         private int mColor;
+        // To be used as the main tint in drawables that wish to have a layer
+        private int mForegroundColor;
 
         public TintedIconManager(
                 ViewGroup group,
@@ -268,26 +271,41 @@
         protected void onIconAdded(int index, String slot, boolean blocked,
                 StatusBarIconHolder holder) {
             StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
-            view.setStaticDrawableColor(mColor);
+            view.setStaticDrawableColor(mColor, mForegroundColor);
             view.setDecorColor(mColor);
         }
 
-        public void setTint(int color) {
-            mColor = color;
+        /**
+         * Most icons are a single layer, and tintColor will be used as the tint in those cases.
+         * For icons that have a background, foregroundColor becomes the contrasting tint used
+         * for the foreground.
+         *
+         * @param tintColor the main tint to use for the icons in the group
+         * @param foregroundColor used as the main tint for layer-ish drawables where tintColor is
+         *                        being used as the background
+         */
+        public void setTint(int tintColor, int foregroundColor) {
+            mColor = tintColor;
+            mForegroundColor = foregroundColor;
+
             for (int i = 0; i < mGroup.getChildCount(); i++) {
                 View child = mGroup.getChildAt(i);
                 if (child instanceof StatusIconDisplayable) {
                     StatusIconDisplayable icon = (StatusIconDisplayable) child;
-                    icon.setStaticDrawableColor(mColor);
+                    icon.setStaticDrawableColor(mColor, mForegroundColor);
                     icon.setDecorColor(mColor);
                 }
             }
+
+            if (mDemoStatusIcons != null) {
+                mDemoStatusIcons.setColor(tintColor, foregroundColor);
+            }
         }
 
         @Override
         protected DemoStatusIcons createDemoStatusIcons() {
             DemoStatusIcons icons = super.createDemoStatusIcons();
-            icons.setColor(mColor);
+            icons.setColor(mColor, mForegroundColor);
             return icons;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
index 6dc8065..da91d6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
@@ -15,6 +15,9 @@
  */
 package com.android.systemui.statusbar.phone;
 
+import com.android.systemui.util.annotations.WeaklyReferencedCallback;
+
+@WeaklyReferencedCallback
 public interface StatusBarWindowCallback {
     /**
      * Invoked when the internal state of NotificationShadeWindowControllerImpl changes.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
new file mode 100644
index 0000000..85fd2af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone
+
+import android.app.Dialog
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.os.Bundle
+import android.view.Gravity
+import android.view.WindowManager
+import com.android.systemui.res.R
+
+/** A dialog shown as a bottom sheet. */
+open class SystemUIBottomSheetDialog(
+    context: Context,
+    theme: Int = R.style.Theme_SystemUI_Dialog,
+) : Dialog(context, theme) {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        window?.apply {
+            setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+            addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+
+            setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+            setGravity(Gravity.BOTTOM)
+            val edgeToEdgeHorizontally =
+                context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog)
+            if (edgeToEdgeHorizontally) {
+                decorView.setPadding(0, 0, 0, 0)
+                setLayout(
+                    WindowManager.LayoutParams.MATCH_PARENT,
+                    WindowManager.LayoutParams.WRAP_CONTENT
+                )
+
+                val lp = attributes
+                lp.fitInsetsSides = 0
+                lp.horizontalMargin = 0f
+                attributes = lp
+            }
+        }
+        setCanceledOnTouchOutside(true)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index aacdc63..3522b9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -190,6 +190,24 @@
     fun logOnSimStateChanged() {
         buffer.log(TAG, LogLevel.INFO, "onSimStateChanged")
     }
+
+    fun logPrioritizedNetworkAvailable(netId: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            { int1 = netId },
+            { "Found prioritized network (nedId=$int1)" },
+        )
+    }
+
+    fun logPrioritizedNetworkLost(netId: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            { int1 = netId },
+            { "Lost prioritized network (nedId=$int1)" },
+        )
+    }
 }
 
 private const val TAG = "MobileInputLog"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index a89b1b2..679426d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -131,6 +131,12 @@
      */
     val isAllowedDuringAirplaneMode: StateFlow<Boolean>
 
+    /**
+     * True if this network has NET_CAPABILITIY_PRIORITIZE_LATENCY, and can be considered to be a
+     * network slice
+     */
+    val hasPrioritizedNetworkCapabilities: StateFlow<Boolean>
+
     companion object {
         /** The default number of levels to use for [numberOfLevels]. */
         const val DEFAULT_NUM_LEVELS = 4
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
index c576b82..caa9d1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -191,6 +191,8 @@
 
     override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
 
+    override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
+
     /**
      * Process a new demo mobile event. Note that [resolvedNetworkType] must be passed in separately
      * from the event, due to the requirement to reverse the mobile mappings lookup in the top-level
@@ -225,6 +227,7 @@
         _resolvedNetworkType.value = resolvedNetworkType
 
         isAllowedDuringAirplaneMode.value = false
+        hasPrioritizedNetworkCapabilities.value = event.slice
     }
 
     fun processCarrierMergedEvent(event: FakeWifiEventModel.CarrierMerged) {
@@ -250,6 +253,7 @@
         _isGsm.value = false
         _carrierNetworkChangeActive.value = false
         isAllowedDuringAirplaneMode.value = true
+        hasPrioritizedNetworkCapabilities.value = false
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
index d4ddb85..4cd877e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
@@ -76,6 +76,7 @@
         val carrierNetworkChange = getString("carriernetworkchange") == "show"
         val roaming = getString("roam") == "show"
         val name = getString("networkname") ?: "demo mode"
+        val slice = getString("slice").toBoolean()
 
         return Mobile(
             level = level,
@@ -87,6 +88,7 @@
             carrierNetworkChange = carrierNetworkChange,
             roaming = roaming,
             name = name,
+            slice = slice,
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
index 8b03f71..0aa95f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
@@ -36,6 +36,7 @@
         val carrierNetworkChange: Boolean,
         val roaming: Boolean,
         val name: String,
+        val slice: Boolean = false,
     ) : FakeNetworkEventModel
 
     data class MobileDisabled(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index 28be3be..27edd1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -174,6 +174,13 @@
      */
     override val isAllowedDuringAirplaneMode = MutableStateFlow(true).asStateFlow()
 
+    /**
+     * It's not currently considered possible that a carrier merged network can have these
+     * prioritized capabilities. If we need to track them, we can add the same check as is in
+     * [MobileConnectionRepositoryImpl].
+     */
+    override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false).asStateFlow()
+
     override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index ee11c06..6b61921 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -309,6 +309,15 @@
                 activeRepo.value.isAllowedDuringAirplaneMode.value,
             )
 
+    override val hasPrioritizedNetworkCapabilities =
+        activeRepo
+            .flatMapLatest { it.hasPrioritizedNetworkCapabilities }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                activeRepo.value.hasPrioritizedNetworkCapabilities.value,
+            )
+
     class Factory
     @Inject
     constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index dc50990..760dd7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -21,6 +21,11 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
 import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
 import android.telephony.CellSignalStrengthCdma
 import android.telephony.ServiceState
@@ -91,6 +96,7 @@
     subscriptionModel: StateFlow<SubscriptionModel?>,
     defaultNetworkName: NetworkNameModel,
     networkNameSeparator: String,
+    connectivityManager: ConnectivityManager,
     private val telephonyManager: TelephonyManager,
     systemUiCarrierConfig: SystemUiCarrierConfig,
     broadcastDispatcher: BroadcastDispatcher,
@@ -374,11 +380,50 @@
     /** Typical mobile connections aren't available during airplane mode. */
     override val isAllowedDuringAirplaneMode = MutableStateFlow(false).asStateFlow()
 
+    /**
+     * Currently, a network with NET_CAPABILITY_PRIORITIZE_LATENCY is the only type of network that
+     * we consider to be a "network slice". _PRIORITIZE_BANDWIDTH may be added in the future. Any of
+     * these capabilities that are used here must also be represented in the
+     * self_certified_network_capabilities.xml config file
+     */
+    @SuppressLint("WrongConstant")
+    private val networkSliceRequest =
+        NetworkRequest.Builder()
+            .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+            .setSubscriptionIds(setOf(subId))
+            .build()
+
+    @SuppressLint("MissingPermission")
+    override val hasPrioritizedNetworkCapabilities: StateFlow<Boolean> =
+        conflatedCallbackFlow {
+                // Our network callback listens only for this.subId && net_cap_prioritize_latency
+                // therefore our state is a simple mapping of whether or not that network exists
+                val callback =
+                    object : NetworkCallback() {
+                        override fun onAvailable(network: Network) {
+                            logger.logPrioritizedNetworkAvailable(network.netId)
+                            trySend(true)
+                        }
+
+                        override fun onLost(network: Network) {
+                            logger.logPrioritizedNetworkLost(network.netId)
+                            trySend(false)
+                        }
+                    }
+
+                connectivityManager.registerNetworkCallback(networkSliceRequest, callback)
+
+                awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+            }
+            .flowOn(bgDispatcher)
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
     class Factory
     @Inject
     constructor(
         private val context: Context,
         private val broadcastDispatcher: BroadcastDispatcher,
+        private val connectivityManager: ConnectivityManager,
         private val telephonyManager: TelephonyManager,
         private val logger: MobileInputLogger,
         private val carrierConfigRepository: CarrierConfigRepository,
@@ -399,6 +444,7 @@
                 subscriptionModel,
                 defaultNetworkName,
                 networkNameSeparator,
+                connectivityManager,
                 telephonyManager.createForSubscriptionId(subId),
                 carrierConfigRepository.getOrCreateConfigForSubId(subId),
                 broadcastDispatcher,
@@ -421,11 +467,17 @@
  */
 sealed interface CallbackEvent {
     data class OnCarrierNetworkChange(val active: Boolean) : CallbackEvent
+
     data class OnDataActivity(val direction: Int) : CallbackEvent
+
     data class OnDataConnectionStateChanged(val dataState: Int) : CallbackEvent
+
     data class OnDataEnabledChanged(val enabled: Boolean) : CallbackEvent
+
     data class OnDisplayInfoChanged(val telephonyDisplayInfo: TelephonyDisplayInfo) : CallbackEvent
+
     data class OnServiceStateChanged(val serviceState: ServiceState) : CallbackEvent
+
     data class OnSignalStrengthChanged(val signalStrength: SignalStrength) : CallbackEvent
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 4bf297c..fe49c07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -76,6 +76,9 @@
     /** Observable for RAT type (network type) indicator */
     val networkTypeIconGroup: StateFlow<NetworkTypeIconModel>
 
+    /** Whether or not to show the slice attribution */
+    val showSliceAttribution: StateFlow<Boolean>
+
     /**
      * Provider name for this network connection. The name can be one of 3 values:
      * 1. The default network name, if one is configured
@@ -238,6 +241,9 @@
                 DefaultIcon(defaultMobileIconGroup.value),
             )
 
+    override val showSliceAttribution: StateFlow<Boolean> =
+        connectionRepository.hasPrioritizedNetworkCapabilities
+
     override val isRoaming: StateFlow<Boolean> =
         combine(
                 connectionRepository.carrierNetworkChangeActive,
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 f0470ca..b93e443 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
@@ -16,11 +16,13 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.ui.binder
 
+import android.annotation.ColorInt
 import android.content.res.ColorStateList
 import android.view.View
 import android.view.View.GONE
 import android.view.View.VISIBLE
 import android.view.ViewGroup
+import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.Space
 import androidx.core.view.isVisible
@@ -28,10 +30,11 @@
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.settingslib.graph.SignalDrawable
-import com.android.systemui.res.R
 import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
@@ -43,6 +46,11 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.launch
 
+private data class Colors(
+    @ColorInt val tint: Int,
+    @ColorInt val contrast: Int,
+)
+
 object MobileIconBinder {
     /** Binds the view to the view-model, continuing to update the former based on the latter */
     @JvmStatic
@@ -57,6 +65,7 @@
         val activityIn = view.requireViewById<ImageView>(R.id.mobile_in)
         val activityOut = view.requireViewById<ImageView>(R.id.mobile_out)
         val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type)
+        val networkTypeContainer = view.requireViewById<FrameLayout>(R.id.mobile_type_container)
         val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
         val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
         val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming)
@@ -70,7 +79,13 @@
         @StatusBarIconView.VisibleState
         val visibilityState: MutableStateFlow<Int> = MutableStateFlow(initialVisibilityState)
 
-        val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+        val iconTint: MutableStateFlow<Colors> =
+            MutableStateFlow(
+                Colors(
+                    tint = DarkIconDispatcher.DEFAULT_ICON_TINT,
+                    contrast = DarkIconDispatcher.DEFAULT_INVERSE_ICON_TINT
+                )
+            )
         val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
 
         var isCollecting = false
@@ -139,7 +154,26 @@
                                 dataTypeId,
                             )
                             dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) }
-                            networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE
+                            networkTypeContainer.visibility =
+                                if (dataTypeId != null) VISIBLE else GONE
+                        }
+                    }
+
+                    // Set the network type background
+                    launch {
+                        viewModel.networkTypeBackground.collect { background ->
+                            networkTypeContainer.setBackgroundResource(background?.res ?: 0)
+
+                            // Tint will invert when this bit changes
+                            if (background?.res != null) {
+                                networkTypeContainer.backgroundTintList =
+                                    ColorStateList.valueOf(iconTint.value.tint)
+                                networkTypeView.imageTintList =
+                                    ColorStateList.valueOf(iconTint.value.contrast)
+                            } else {
+                                networkTypeView.imageTintList =
+                                    ColorStateList.valueOf(iconTint.value.tint)
+                            }
                         }
                     }
 
@@ -164,14 +198,24 @@
 
                     // Set the tint
                     launch {
-                        iconTint.collect { tint ->
-                            val tintList = ColorStateList.valueOf(tint)
-                            iconView.imageTintList = tintList
-                            networkTypeView.imageTintList = tintList
-                            roamingView.imageTintList = tintList
-                            activityIn.imageTintList = tintList
-                            activityOut.imageTintList = tintList
-                            dotView.setDecorColor(tint)
+                        iconTint.collect { colors ->
+                            val tint = ColorStateList.valueOf(colors.tint)
+                            val contrast = ColorStateList.valueOf(colors.contrast)
+
+                            iconView.imageTintList = tint
+
+                            // If the bg is visible, tint it and use the contrast for the fg
+                            if (viewModel.networkTypeBackground.value != null) {
+                                networkTypeContainer.backgroundTintList = tint
+                                networkTypeView.imageTintList = contrast
+                            } else {
+                                networkTypeView.imageTintList = tint
+                            }
+
+                            roamingView.imageTintList = tint
+                            activityIn.imageTintList = tint
+                            activityOut.imageTintList = tint
+                            dotView.setDecorColor(colors.tint)
                         }
                     }
 
@@ -196,8 +240,8 @@
                 visibilityState.value = state
             }
 
-            override fun onIconTintChanged(newTint: Int) {
-                iconTint.value = newTint
+            override fun onIconTintChanged(newTint: Int, contrastTint: Int) {
+                iconTint.value = Colors(tint = newTint, contrast = contrastTint)
             }
 
             override fun onDecorTintChanged(newTint: Int) {
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 dfabeea..d88c9ef 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
@@ -19,7 +19,10 @@
 import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags.NEW_NETWORK_SLICE_UI
 import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
@@ -47,6 +50,8 @@
     val roaming: Flow<Boolean>
     /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
     val networkTypeIcon: Flow<Icon.Resource?>
+    /** The slice attribution. Drawn as a background layer */
+    val networkTypeBackground: StateFlow<Icon.Resource?>
     val activityInVisible: Flow<Boolean>
     val activityOutVisible: Flow<Boolean>
     val activityContainerVisible: Flow<Boolean>
@@ -67,12 +72,12 @@
  */
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
-class MobileIconViewModel
-constructor(
+class MobileIconViewModel(
     override val subscriptionId: Int,
     iconInteractor: MobileIconInteractor,
     airplaneModeInteractor: AirplaneModeInteractor,
     constants: ConnectivityConstants,
+    flags: FeatureFlagsClassic,
     scope: CoroutineScope,
 ) : MobileIconViewModelCommon {
     override val isVisible: StateFlow<Boolean> =
@@ -152,6 +157,20 @@
             .distinctUntilChanged()
             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
 
+    override val networkTypeBackground =
+        if (!flags.isEnabled(NEW_NETWORK_SLICE_UI)) {
+                flowOf(null)
+            } else {
+                iconInteractor.showSliceAttribution.map {
+                    if (it) {
+                        Icon.Resource(R.drawable.mobile_network_type_background, null)
+                    } else {
+                        null
+                    }
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
     override val roaming: StateFlow<Boolean> =
         iconInteractor.isRoaming
             .logDiffsForTable(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 0f55910..be843ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -19,6 +19,7 @@
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
@@ -54,6 +55,7 @@
     private val interactor: MobileIconsInteractor,
     private val airplaneModeInteractor: AirplaneModeInteractor,
     private val constants: ConnectivityConstants,
+    private val flags: FeatureFlagsClassic,
     @Application private val scope: CoroutineScope,
 ) {
     @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>()
@@ -113,6 +115,7 @@
                     interactor.getMobileConnectionInteractorForSubId(subId),
                     airplaneModeInteractor,
                     constants,
+                    flags,
                     scope,
                 )
                 .also { mobileIconSubIdCache[subId] = it }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
index 81f8683..790596e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
@@ -32,8 +32,8 @@
     /** Notifies that the visibility state has changed. */
     fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
 
-    /** Notifies that the icon tint has been updated. */
-    fun onIconTintChanged(newTint: Int)
+    /** Notifies that the icon tint has been updated. Includes a contrast for layered drawables */
+    fun onIconTintChanged(newTint: Int, contrastTint: Int)
 
     /** Notifies that the decor tint has been updated (used only for the dot). */
     fun onDecorTintChanged(newTint: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
index fe69d81..3b87bed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -20,8 +20,8 @@
 import android.graphics.Rect
 import android.util.AttributeSet
 import android.view.Gravity
-import com.android.systemui.res.R
 import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.BaseStatusBarFrameLayout
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
@@ -51,13 +51,23 @@
     override fun getSlot() = slot
 
     override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
+        // nop
+    }
+
+    override fun onDarkChangedWithContrast(areas: ArrayList<Rect>, tint: Int, contrastTint: Int) {
         val newTint = DarkIconDispatcher.getTint(areas, this, tint)
-        binding.onIconTintChanged(newTint)
+        val contrast = DarkIconDispatcher.getInverseTint(areas, this, contrastTint)
+
+        binding.onIconTintChanged(newTint, contrast)
         binding.onDecorTintChanged(newTint)
     }
 
     override fun setStaticDrawableColor(color: Int) {
-        binding.onIconTintChanged(color)
+        // nop
+    }
+
+    override fun setStaticDrawableColor(color: Int, foregroundColor: Int) {
+        binding.onIconTintChanged(color, foregroundColor)
     }
 
     override fun setDecorColor(color: Int) {
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 a9ac51d..6005527 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,9 +23,9 @@
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.res.R
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
@@ -165,7 +165,7 @@
                 visibilityState.value = state
             }
 
-            override fun onIconTintChanged(newTint: Int) {
+            override fun onIconTintChanged(newTint: Int, contrastTint: Int /* unused */) {
                 iconTint.value = newTint
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java b/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java
new file mode 100644
index 0000000..855bba6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java
@@ -0,0 +1,35 @@
+/*
+ * 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.util.annotations;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Descriptive annotation for clearly tagging callback types that are weakly
+ * referenced during registration.
+ *
+ * This is useful in providing hints to Proguard about certain fields that
+ * should be kept to preserve strong references.
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target({TYPE})
+public @interface WeaklyReferencedCallback {}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
index 968dcc9..df5162a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
@@ -26,6 +26,7 @@
 
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.annotations.WeaklyReferencedCallback;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -64,6 +65,7 @@
      * An interface for listening to the connection status.
      * @param <T> The wrapper type.
      */
+    @WeaklyReferencedCallback
     public interface Callback<T> {
         /**
          * Invoked when the service has been successfully connected to.
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java
index 7687432..425336d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.util.service;
 
+import com.android.systemui.util.annotations.WeaklyReferencedCallback;
+
 /**
  * The {@link Observer} interface specifies an entity which listeners
  * can be informed of changes to the source, which will require updating. Note that this deals
@@ -25,6 +27,7 @@
     /**
      * Callback for receiving updates from the {@link Observer}.
      */
+    @WeaklyReferencedCallback
     interface Callback {
         /**
          * Invoked when the source has changed.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
deleted file mode 100644
index 712eef1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
+++ /dev/null
@@ -1,108 +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.biometrics.domain.interactor
-
-import android.hardware.biometrics.SensorLocationInternal
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.coroutines.collectLastValue
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.junit.MockitoJUnit
-
-@SmallTest
-@RunWith(JUnit4::class)
-class SideFpsOverlayInteractorTest : SysuiTestCase() {
-
-    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
-    private lateinit var testScope: TestScope
-
-    private val fingerprintRepository = FakeFingerprintPropertyRepository()
-
-    private lateinit var interactor: SideFpsOverlayInteractor
-
-    @Before
-    fun setup() {
-        testScope = TestScope(StandardTestDispatcher())
-        interactor = SideFpsOverlayInteractorImpl(fingerprintRepository)
-    }
-
-    @Test
-    fun testOverlayOffsetUpdates() =
-        testScope.runTest {
-            fingerprintRepository.setProperties(
-                sensorId = 1,
-                strength = SensorStrength.STRONG,
-                sensorType = FingerprintSensorType.REAR,
-                sensorLocations =
-                    mapOf(
-                        "" to
-                            SensorLocationInternal(
-                                "" /* displayId */,
-                                540 /* sensorLocationX */,
-                                1636 /* sensorLocationY */,
-                                130 /* sensorRadius */
-                            ),
-                        "display_id_1" to
-                            SensorLocationInternal(
-                                "display_id_1" /* displayId */,
-                                100 /* sensorLocationX */,
-                                300 /* sensorLocationY */,
-                                20 /* sensorRadius */
-                            )
-                    )
-            )
-
-            val displayId by collectLastValue(interactor.displayId)
-            val offsets by collectLastValue(interactor.overlayOffsets)
-
-            // Assert offsets of empty displayId.
-            assertThat(displayId).isEqualTo("")
-            assertThat(offsets?.displayId).isEqualTo("")
-            assertThat(offsets?.sensorLocationX).isEqualTo(540)
-            assertThat(offsets?.sensorLocationY).isEqualTo(1636)
-            assertThat(offsets?.sensorRadius).isEqualTo(130)
-
-            // Offsets should be updated correctly.
-            interactor.onDisplayChanged("display_id_1")
-            assertThat(displayId).isEqualTo("display_id_1")
-            assertThat(offsets?.displayId).isEqualTo("display_id_1")
-            assertThat(offsets?.sensorLocationX).isEqualTo(100)
-            assertThat(offsets?.sensorLocationY).isEqualTo(300)
-            assertThat(offsets?.sensorRadius).isEqualTo(20)
-
-            // Should return default offset when the displayId is invalid.
-            interactor.onDisplayChanged("invalid_display_id")
-            assertThat(displayId).isEqualTo("invalid_display_id")
-            assertThat(offsets?.displayId).isEqualTo(SensorLocationInternal.DEFAULT.displayId)
-            assertThat(offsets?.sensorLocationX)
-                .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationX)
-            assertThat(offsets?.sensorLocationY)
-                .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationY)
-            assertThat(offsets?.sensorRadius).isEqualTo(SensorLocationInternal.DEFAULT.sensorRadius)
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
new file mode 100644
index 0000000..99501c42
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
@@ -0,0 +1,410 @@
+/*
+ * 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.biometrics.domain.interactor
+
+import android.graphics.Rect
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.display.DisplayManagerGlobal
+import android.view.Display
+import android.view.DisplayInfo
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.view.WindowMetrics
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_0
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_180
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_270
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_90
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags.REST_TO_UNLOCK
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+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.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.junit.MockitoJUnit
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class SideFpsSensorInteractorTest : SysuiTestCase() {
+
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+    private lateinit var testScope: TestScope
+
+    private val fingerprintRepository = FakeFingerprintPropertyRepository()
+
+    private lateinit var underTest: SideFpsSensorInteractor
+
+    @Mock private lateinit var windowManager: WindowManager
+    @Mock private lateinit var displayStateInteractor: DisplayStateInteractor
+
+    private val contextDisplayInfo = DisplayInfo()
+    private val displayChangeEvent = MutableStateFlow(0)
+    private val currentRotation = MutableStateFlow(ROTATION_0)
+
+    @Before
+    fun setup() {
+        testScope = TestScope(StandardTestDispatcher())
+        mContext = spy(mContext)
+
+        val displayManager = mock(DisplayManagerGlobal::class.java)
+        val resources = mContext.resources
+        whenever(mContext.display)
+            .thenReturn(Display(displayManager, 1, contextDisplayInfo, resources))
+        whenever(displayStateInteractor.displayChanges).thenReturn(displayChangeEvent)
+        whenever(displayStateInteractor.currentRotation).thenReturn(currentRotation)
+
+        contextDisplayInfo.uniqueId = "current-display"
+
+        underTest =
+            SideFpsSensorInteractor(
+                mContext,
+                fingerprintRepository,
+                windowManager,
+                displayStateInteractor,
+                FakeFeatureFlagsClassic().apply { set(REST_TO_UNLOCK, true) }
+            )
+    }
+
+    @Test
+    fun testSfpsSensorAvailable() =
+        testScope.runTest {
+            val isAvailable by collectLastValue(underTest.isAvailable)
+
+            setupFingerprint(FingerprintSensorType.POWER_BUTTON)
+            assertThat(isAvailable).isTrue()
+
+            setupFingerprint(FingerprintSensorType.HOME_BUTTON)
+            assertThat(isAvailable).isFalse()
+
+            setupFingerprint(FingerprintSensorType.REAR)
+            assertThat(isAvailable).isFalse()
+
+            setupFingerprint(FingerprintSensorType.UDFPS_OPTICAL)
+            assertThat(isAvailable).isFalse()
+
+            setupFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC)
+            assertThat(isAvailable).isFalse()
+
+            setupFingerprint(FingerprintSensorType.UNKNOWN)
+            assertThat(isAvailable).isFalse()
+        }
+
+    @Test
+    fun authenticationDurationIsAvailableWhenSFPSSensorIsAvailable() =
+        testScope.runTest {
+            assertThat(collectLastValue(underTest.authenticationDuration)())
+                .isEqualTo(context.resources.getInteger(R.integer.config_restToUnlockDuration))
+        }
+
+    @Test
+    fun verticalSensorLocationIsAdjustedToScreenPositionForRotation0() =
+        testScope.runTest {
+            /*
+            (0,0)                (1000,0)
+               ------------------
+              |  ^^^^^           |  (1000, 200)
+              |   status bar    || <--- start of sensor at Rotation_0
+              |                 || <--- end of sensor
+              |                  |  (1000, 300)
+              |                  |
+               ------------------ (1000, 800)
+             */
+            setupFPLocationAndDisplaySize(
+                width = 1000,
+                height = 800,
+                rotation = ROTATION_0,
+                sensorLocationY = 200,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(1000)
+            assertThat(sensorLocation!!.top).isEqualTo(200)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    @Test
+    fun verticalSensorLocationIsAdjustedToScreenPositionForRotation270() =
+        testScope.runTest {
+            /*
+            (800,0)                     (800, 1000)
+                  ---------------------
+                 |                     | (600, 1000)
+                 |   <                || <--- end of sensor at Rotation_270
+                 |   < status bar     || <--- start of sensor
+                 |   <                 | (500, 1000)
+                 |   <                 |
+            (0,0) ---------------------
+             */
+            setupFPLocationAndDisplaySize(
+                width = 800,
+                height = 1000,
+                rotation = ROTATION_270,
+                sensorLocationY = 200,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(500)
+            assertThat(sensorLocation!!.top).isEqualTo(1000)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    @Test
+    fun verticalSensorLocationIsAdjustedToScreenPositionForRotation90() =
+        testScope.runTest {
+            /*
+                                            (0,0)
+                       ---------------------
+                      |                     | (200, 0)
+                      |               >    || <--- end of sensor at Rotation_270
+                      |    status bar >    || <--- start of sensor
+                      |               >     | (300, 0)
+                      |               >     |
+            (800,1000) ---------------------
+             */
+            setupFPLocationAndDisplaySize(
+                width = 800,
+                height = 1000,
+                rotation = ROTATION_90,
+                sensorLocationY = 200,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(200)
+            assertThat(sensorLocation!!.top).isEqualTo(0)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    @Test
+    fun verticalSensorLocationIsAdjustedToScreenPositionForRotation180() =
+        testScope.runTest {
+            /*
+
+            (1000,800) ---------------------
+                      |                     | (0, 600)
+                      |                    || <--- end of sensor at Rotation_270
+                      |    status bar      || <--- start of sensor
+                      |   \/\/\/\/\/\/\/    | (0, 500)
+                      |                     |
+                       ---------------------  (0,0)
+             */
+            setupFPLocationAndDisplaySize(
+                width = 1000,
+                height = 800,
+                rotation = ROTATION_180,
+                sensorLocationY = 200,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(0)
+            assertThat(sensorLocation!!.top).isEqualTo(500)
+        }
+
+    @Test
+    fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation0() =
+        testScope.runTest {
+            /*
+            (0,0)   (500,0)   (600,0)   (1000,0)
+               ____________===_________
+              |                        |
+              |  ^^^^^                 |
+              |   status bar           |
+              |                        |
+               ------------------------ (1000, 800)
+             */
+            setupFPLocationAndDisplaySize(
+                width = 1000,
+                height = 800,
+                rotation = ROTATION_0,
+                sensorLocationX = 500,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(500)
+            assertThat(sensorLocation!!.top).isEqualTo(0)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    @Test
+    fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation90() =
+        testScope.runTest {
+            /*
+                  (0,1000)   (0,500)   (0,400)   (0,0)
+                        ____________===_________
+                       |                        |
+                       |               >        |
+                       |   status bar  >        |
+                       |               >        |
+            (800, 1000) ------------------------
+             */
+            setupFPLocationAndDisplaySize(
+                width = 800,
+                height = 1000,
+                rotation = ROTATION_90,
+                sensorLocationX = 500,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(0)
+            assertThat(sensorLocation!!.top).isEqualTo(400)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    @Test
+    fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation180() =
+        testScope.runTest {
+            /*
+            (1000, 800)  (500, 800)   (400, 800)   (0,800)
+                       ____________===_________
+                      |                        |
+                      |                        |
+                      |   status bar           |
+                      |  \/ \/ \/ \/ \/ \/ \/  |
+                       ------------------------ (0,0)
+             */
+            setupFPLocationAndDisplaySize(
+                width = 1000,
+                height = 800,
+                rotation = ROTATION_180,
+                sensorLocationX = 500,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(400)
+            assertThat(sensorLocation!!.top).isEqualTo(800)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    @Test
+    fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation270() =
+        testScope.runTest {
+            /*
+                        (800, 500)  (800, 600)
+            (800, 0) ____________===_________ (800,1000)
+                    |  <                     |
+                    |  <                     |
+                    |  < status bar          |
+                    |  <                     |
+               (0,0) ------------------------
+             */
+            setupFPLocationAndDisplaySize(
+                width = 800,
+                height = 1000,
+                rotation = ROTATION_270,
+                sensorLocationX = 500,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(800)
+            assertThat(sensorLocation!!.top).isEqualTo(500)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    private suspend fun TestScope.setupFPLocationAndDisplaySize(
+        width: Int,
+        height: Int,
+        sensorLocationX: Int = 0,
+        sensorLocationY: Int = 0,
+        rotation: DisplayRotation,
+        sensorWidth: Int
+    ) {
+        overrideResource(R.integer.config_sfpsSensorWidth, sensorWidth)
+        setupDisplayDimensions(width, height)
+        currentRotation.value = rotation
+        setupFingerprint(x = sensorLocationX, y = sensorLocationY, displayId = "expanded_display")
+    }
+
+    private fun setupDisplayDimensions(displayWidth: Int, displayHeight: Int) {
+        whenever(windowManager.maximumWindowMetrics)
+            .thenReturn(
+                WindowMetrics(
+                    Rect(0, 0, displayWidth, displayHeight),
+                    mock(WindowInsets::class.java)
+                )
+            )
+    }
+
+    private suspend fun TestScope.setupFingerprint(
+        fingerprintSensorType: FingerprintSensorType = FingerprintSensorType.POWER_BUTTON,
+        x: Int = 0,
+        y: Int = 0,
+        displayId: String = "display_id_1"
+    ) {
+        contextDisplayInfo.uniqueId = displayId
+        fingerprintRepository.setProperties(
+            sensorId = 1,
+            strength = SensorStrength.STRONG,
+            sensorType = fingerprintSensorType,
+            sensorLocations =
+                mapOf(
+                    "someOtherDisplayId" to
+                        SensorLocationInternal(
+                            "someOtherDisplayId",
+                            x + 100,
+                            y + 100,
+                            0,
+                        ),
+                    displayId to
+                        SensorLocationInternal(
+                            displayId,
+                            x,
+                            y,
+                            0,
+                        )
+                )
+        )
+        // Emit a display change event, this happens whenever any display related change happens,
+        // rotation, active display changing etc, display switched off/on.
+        displayChangeEvent.emit(1)
+
+        runCurrent()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
index 37c70d8..2bd2bff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
@@ -43,10 +43,10 @@
     fun accessingUnspecifiedFlags_throwsException() {
         val flags: FeatureFlags = FakeFeatureFlags()
         try {
-            assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse()
+            assertThat(flags.isEnabled(Flags.NULL_FLAG)).isFalse()
             fail("Expected an exception when accessing an unspecified flag.")
         } catch (ex: IllegalStateException) {
-            assertThat(ex.message).contains("UNKNOWN(teamfood)")
+            assertThat(ex.message).contains("UNKNOWN(null_flag)")
         }
         try {
             assertThat(flags.isEnabled(unreleasedFlag)).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
index c12a581..f51745b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
@@ -22,6 +22,7 @@
 import android.content.res.Resources
 import android.content.res.Resources.NotFoundException
 import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.FakeFeatureFlagsImpl
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -65,6 +66,7 @@
     private lateinit var broadcastReceiver: BroadcastReceiver
     private lateinit var clearCacheAction: Consumer<String>
     private val serverFlagReader = ServerFlagReaderFake()
+    private val fakeGantryFlags = FakeFeatureFlagsImpl()
 
     private val teamfoodableFlagA = UnreleasedFlag(name = "a", namespace = "test", teamfood = true)
     private val teamfoodableFlagB = ReleasedFlag(name = "b", namespace = "test", teamfood = true)
@@ -72,7 +74,7 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        flagMap.put(Flags.TEAMFOOD.name, Flags.TEAMFOOD)
+        fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", false)
         flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
         flagMap.put(teamfoodableFlagB.name, teamfoodableFlagB)
         mFeatureFlagsClassicDebug =
@@ -84,6 +86,7 @@
                 resources,
                 serverFlagReader,
                 flagMap,
+                fakeGantryFlags,
                 restarter
             )
         mFeatureFlagsClassicDebug.init()
@@ -121,8 +124,6 @@
 
     @Test
     fun teamFoodFlag_False() {
-        whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any()))
-            .thenReturn(false)
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isFalse()
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue()
 
@@ -133,8 +134,7 @@
 
     @Test
     fun teamFoodFlag_True() {
-        whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any()))
-            .thenReturn(true)
+        fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", true)
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue()
 
@@ -149,8 +149,7 @@
             .thenReturn(true)
         whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.name), any()))
             .thenReturn(false)
-        whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any()))
-            .thenReturn(true)
+        fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", true)
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isFalse()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index e537131..4c5a214 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -54,11 +54,7 @@
 import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.statusbar.connectivity.WifiIndicators;
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository;
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository;
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor;
-import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl;
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
 import com.android.systemui.statusbar.policy.HotspotController;
@@ -111,7 +107,8 @@
 
     private WifiInteractor mWifiInteractor;
     private final TileJavaAdapter mJavaAdapter = new TileJavaAdapter();
-    private final FakeWifiRepository mWifiRepository = new FakeWifiRepository();
+    private final FakeConnectivityRepository mConnectivityRepository =
+            new FakeConnectivityRepository();
     private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     private final TestScope mTestScope = TestScopeProvider.getTestScope();
 
@@ -124,12 +121,6 @@
         mTestableLooper = TestableLooper.get(this);
 
         when(mHost.getContext()).thenReturn(mContext);
-
-        mWifiInteractor = new WifiInteractorImpl(
-                new FakeConnectivityRepository(),
-                mWifiRepository,
-                mTestScope
-        );
     }
 
     @After
@@ -204,25 +195,41 @@
     // SIGNAL_CALLBACK_DEPRECATION flag set to true
 
     @Test
-    public void stateUnavailable_wifiDisabled_newPipeline() {
+    public void stateUnavailable_noDefaultNetworks_newPipeline() {
         createAndStartTileNewImpl();
-        mWifiRepository.setIsWifiEnabled(false);
         mTestableLooper.processAllMessages();
 
         assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
     }
 
     @Test
-    public void stateUnavailable_wifiEnabled_notConnected_newPipeline() {
+    public void stateUnavailable_mobileConnected_newPipeline() {
         createAndStartTileNewImpl();
-        mWifiRepository.setIsWifiEnabled(true);
-        mWifiRepository.setWifiNetwork(Inactive.INSTANCE);
+        mConnectivityRepository.setMobileConnected(true);
         mTestableLooper.processAllMessages();
 
         assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
     }
 
     @Test
+    public void stateInactive_wifiConnected_newPipeline() {
+        createAndStartTileNewImpl();
+        mConnectivityRepository.setWifiConnected(true);
+        mTestableLooper.processAllMessages();
+
+        assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+    }
+
+    @Test
+    public void stateInactive_ethernetConnected_newPipeline() {
+        createAndStartTileNewImpl();
+        mConnectivityRepository.setEthernetConnected(true);
+        mTestableLooper.processAllMessages();
+
+        assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+    }
+
+    @Test
     public void stateActive_wifiConnectedAndCasting_newPipeline() {
         createAndStartTileNewImpl();
         CastController.CastDevice device = new CastController.CastDevice();
@@ -231,40 +238,27 @@
         devices.add(device);
         when(mController.getCastDevices()).thenReturn(devices);
 
-        mWifiRepository.setWifiNetwork(
-                new WifiNetworkModel.Active(
-                        1 /* networkId */,
-                        true /* isValidated */,
-                        3 /* level */,
-                        "test" /* ssid */,
-                        WifiNetworkModel.HotspotDeviceType.NONE,
-                        false /* isPasspointAccessPoint */,
-                        false /* isOnlineSignUpforPasspointAccessPoint */,
-                        null /* passpointProviderFriendlyName */
-                ));
+        mConnectivityRepository.setWifiConnected(true);
+
         mTestableLooper.processAllMessages();
 
         assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
     }
 
     @Test
-    public void stateInactive_wifiConnectedNotCasting_newPipeline() {
+    public void stateActive_ethernetConnectedAndCasting_newPipeline() {
         createAndStartTileNewImpl();
+        CastController.CastDevice device = new CastController.CastDevice();
+        device.state = CastDevice.STATE_CONNECTED;
+        List<CastDevice> devices = new ArrayList<>();
+        devices.add(device);
+        when(mController.getCastDevices()).thenReturn(devices);
 
-        mWifiRepository.setWifiNetwork(
-                new WifiNetworkModel.Active(
-                        1 /* networkId */,
-                        true /* isValidated */,
-                        3 /* level */,
-                        "test" /* ssid */,
-                        WifiNetworkModel.HotspotDeviceType.NONE,
-                        false /* isPasspointAccessPoint */,
-                        false /* isOnlineSignUpforPasspointAccessPoint */,
-                        null /* passpointProviderFriendlyName */
-                ));
+        mConnectivityRepository.setEthernetConnected(true);
+
         mTestableLooper.processAllMessages();
 
-        assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+        assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
     }
 
     // -------------------------------------------------
@@ -512,7 +506,7 @@
                 mNetworkController,
                 mHotspotController,
                 mDialogLaunchAnimator,
-                mWifiInteractor,
+                mConnectivityRepository,
                 mJavaAdapter,
                 mFeatureFlags
         );
@@ -555,7 +549,7 @@
                 mNetworkController,
                 mHotspotController,
                 mDialogLaunchAnimator,
-                mWifiInteractor,
+                mConnectivityRepository,
                 mJavaAdapter,
                 mFeatureFlags
         );
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 58e36be..10c7c43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -21,6 +21,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -49,6 +51,7 @@
     private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
     private var mobileIconsViewModel: MobileIconsViewModel =
         MobileIconsViewModel(
@@ -61,6 +64,7 @@
                     FakeConnectivityRepository(),
                 ),
             constants = mock(),
+            flags,
             scope = testScope.backgroundScope,
         )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 61dd69a..88a5c17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -25,6 +25,8 @@
 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
 import com.android.systemui.model.SysUiState
@@ -137,6 +139,7 @@
         )
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
     private var mobileIconsViewModel: MobileIconsViewModel =
         MobileIconsViewModel(
@@ -149,6 +152,7 @@
                     FakeConnectivityRepository(),
                 ),
             constants = mock(),
+            flags,
             scope = testScope.backgroundScope,
         )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
index 3ae1f35..da4dc85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertEquals
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -111,6 +112,15 @@
     }
 
     @Test
+    fun showDialog_singleAppIsDefault() {
+        dialog.show()
+
+        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+        val singleApp = context.getString(R.string.screen_share_permission_dialog_option_single_app)
+        assertEquals(spinner.adapter.getItem(0), singleApp)
+    }
+
+    @Test
     fun showDialog_cancelClicked_dialogIsDismissed() {
         dialog.show()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index a105c15..d8821aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -63,8 +63,8 @@
 
     @Before
     fun setUp() {
-        whenever(controllerFactory.create(eq(0))).thenReturn(controller0)
-        whenever(controllerFactory.create(eq(1))).thenReturn(controller1)
+        whenever(controllerFactory.create(eq(0), any())).thenReturn(controller0)
+        whenever(controllerFactory.create(eq(1), any())).thenReturn(controller1)
     }
 
     @Test
@@ -74,8 +74,8 @@
             val onSaved = { _: Uri -> }
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
 
-            verify(controllerFactory).create(eq(0))
-            verify(controllerFactory).create(eq(1))
+            verify(controllerFactory).create(eq(0), any())
+            verify(controllerFactory).create(eq(1), any())
 
             val capturer = ArgumentCaptor<ScreenshotData>()
 
@@ -107,8 +107,8 @@
                 callback
             )
 
-            verify(controllerFactory).create(eq(0))
-            verify(controllerFactory, never()).create(eq(1))
+            verify(controllerFactory).create(eq(0), any())
+            verify(controllerFactory, never()).create(eq(1), any())
 
             val capturer = ArgumentCaptor<ScreenshotData>()
 
@@ -139,7 +139,7 @@
     @Test
     fun executeScreenshots_allowedTypes_allCaptured() =
         testScope.runTest {
-            whenever(controllerFactory.create(any())).thenReturn(controller0)
+            whenever(controllerFactory.create(any(), any())).thenReturn(controller0)
 
             setDisplays(
                 display(TYPE_INTERNAL, id = 0),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 6205d90..5091a70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -86,7 +86,7 @@
             )
             .thenReturn(false)
         whenever(userManager.isUserUnlocked).thenReturn(true)
-        whenever(controllerFactory.create(any())).thenReturn(controller)
+        whenever(controllerFactory.create(any(), any())).thenReturn(controller)
 
         // Stub request processor as a synchronous no-op for tests with the flag enabled
         doAnswer {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 8d8c70e..4f0cec5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -157,6 +157,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
 import com.android.systemui.statusbar.phone.LightBarController;
@@ -328,6 +329,7 @@
     @Mock private CastController mCastController;
     @Mock private KeyguardRootView mKeyguardRootView;
     @Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
+    @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm;
 
     protected final int mMaxUdfpsBurnInOffsetY = 5;
     protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
@@ -667,7 +669,8 @@
                 mKeyguardViewConfigurator,
                 mKeyguardFaceAuthInteractor,
                 new ResourcesSplitShadeStateController(),
-                mPowerInteractor);
+                mPowerInteractor,
+                mKeyguardClockPositionAlgorithm);
         mNotificationPanelViewController.initDependencies(
                 mCentralSurfaces,
                 null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 607cdab..df38f93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -4,6 +4,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
@@ -31,6 +33,7 @@
     private val sceneInteractor = utils.sceneInteractor()
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
     private var mobileIconsViewModel: MobileIconsViewModel =
         MobileIconsViewModel(
@@ -43,6 +46,7 @@
                     FakeConnectivityRepository(),
                 ),
             constants = mock(),
+            flags,
             scope = testScope.backgroundScope,
         )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index c423782..3064f4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -21,6 +21,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -55,6 +57,7 @@
         )
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
     private var mobileIconsViewModel: MobileIconsViewModel =
         MobileIconsViewModel(
@@ -67,6 +70,7 @@
                     FakeConnectivityRepository(),
                 ),
             constants = mock(),
+            flags,
             scope = testScope.backgroundScope,
         )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java
new file mode 100644
index 0000000..3a9c24a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java
@@ -0,0 +1,597 @@
+/*
+ * 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;
+
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
+import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.KeyguardManager;
+import android.app.Notification;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.FakeSettings;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationLockscreenUserManagerMainThreadTest extends SysuiTestCase {
+    @Mock
+    private NotificationPresenter mPresenter;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private UserTracker mUserTracker;
+
+    // Dependency mocks:
+    @Mock
+    private NotificationVisibilityProvider mVisibilityProvider;
+    @Mock
+    private CommonNotifCollection mNotifCollection;
+    @Mock
+    private DevicePolicyManager mDevicePolicyManager;
+    @Mock
+    private NotificationClickNotifier mClickNotifier;
+    @Mock
+    private OverviewProxyService mOverviewProxyService;
+    @Mock
+    private KeyguardManager mKeyguardManager;
+    @Mock
+    private DeviceProvisionedController mDeviceProvisionedController;
+    @Mock
+    private StatusBarStateController mStatusBarStateController;
+    @Mock
+    private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+
+    private UserInfo mCurrentUser;
+    private UserInfo mSecondaryUser;
+    private UserInfo mWorkUser;
+    private FakeSettings mSettings;
+    private TestNotificationLockscreenUserManager mLockscreenUserManager;
+    private NotificationEntry mCurrentUserNotif;
+    private NotificationEntry mSecondaryUserNotif;
+    private NotificationEntry mWorkProfileNotif;
+    private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
+    private Executor mBackgroundExecutor = Runnable::run; // Direct executor
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
+
+        int currentUserId = ActivityManager.getCurrentUser();
+        when(mUserTracker.getUserId()).thenReturn(currentUserId);
+        mSettings = new FakeSettings();
+        mSettings.setUserId(ActivityManager.getCurrentUser());
+        mCurrentUser = new UserInfo(currentUserId, "", 0);
+        mSecondaryUser = new UserInfo(currentUserId + 1, "", 0);
+        mWorkUser = new UserInfo(currentUserId + 2, "" /* name */, null /* iconPath */, 0,
+                UserManager.USER_TYPE_PROFILE_MANAGED);
+
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);
+        when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(
+                mCurrentUser, mWorkUser));
+        when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(
+                mSecondaryUser));
+        mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
+                Handler.createAsync(Looper.myLooper()));
+
+        Notification notifWithPrivateVisibility = new Notification();
+        notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE;
+        mCurrentUserNotif = new NotificationEntryBuilder()
+                .setNotification(notifWithPrivateVisibility)
+                .setUser(new UserHandle(mCurrentUser.id))
+                .build();
+        mSecondaryUserNotif = new NotificationEntryBuilder()
+                .setNotification(notifWithPrivateVisibility)
+                .setUser(new UserHandle(mSecondaryUser.id))
+                .build();
+        mWorkProfileNotif = new NotificationEntryBuilder()
+                .setNotification(notifWithPrivateVisibility)
+                .setUser(new UserHandle(mWorkUser.id))
+                .build();
+
+        mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
+        mLockscreenUserManager.setUpWithPresenter(mPresenter);
+    }
+
+    private void changeSetting(String setting) {
+        final Collection<Uri> lockScreenUris = new ArrayList<>();
+        lockScreenUris.add(Settings.Secure.getUriFor(setting));
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false,
+            lockScreenUris, 0);
+    }
+
+    @Test
+    public void testLockScreenShowNotificationsFalse() {
+        mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+        assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
+    }
+
+    @Test
+    public void testLockScreenShowNotificationsTrue() {
+        mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+        assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
+    }
+
+    @Test
+    public void testLockScreenAllowPrivateNotificationsTrue() {
+        mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testLockScreenAllowPrivateNotificationsFalse() {
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testLockScreenAllowsWorkPrivateNotificationsFalse() {
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mWorkUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
+    }
+
+    @Test
+    public void testLockScreenAllowsWorkPrivateNotificationsTrue() {
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mWorkUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
+    }
+
+    @Test
+    public void testCurrentUserPrivateNotificationsNotRedacted() {
+        // GIVEN current user doesn't allow private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN current user's notification is redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+    }
+
+    @Test
+    public void testCurrentUserPrivateNotificationsRedacted() {
+        // GIVEN current user allows private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN current user's notification isn't redacted
+        assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+    }
+
+    @Test
+    public void testWorkPrivateNotificationsRedacted() {
+        // GIVEN work profile doesn't private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mWorkUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN work profile notification is redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+        assertFalse(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
+    }
+
+    @Test
+    public void testWorkPrivateNotificationsNotRedacted() {
+        // GIVEN work profile allows private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mWorkUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN work profile notification isn't redacted
+        assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+        assertTrue(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
+    }
+
+    @Test
+    public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() {
+        // GIVEN work profile allows private notifications to show but the other users don't
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mWorkUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN the work profile notification doesn't need to be redacted
+        assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+
+        // THEN the current user and secondary user notifications do need to be redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+    }
+
+    @Test
+    public void testWorkProfileRedacted_otherUsersNotRedacted() {
+        // GIVEN work profile doesn't allow private notifications to show but the other users do
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mWorkUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mCurrentUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN the work profile notification needs to be redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+
+        // THEN the current user and secondary user notifications don't need to be redacted
+        assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+        assertFalse(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+    }
+
+    @Test
+    public void testSecondaryUserNotRedacted_currentUserRedacted() {
+        // GIVEN secondary profile allows private notifications to show but the current user
+        // doesn't allow private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN the secondary profile notification still needs to be redacted because the current
+        // user's setting takes precedence
+        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+    }
+
+    @Test
+    public void testUserSwitchedCallsOnUserSwitching() {
+        mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id,
+                mContext);
+        verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id);
+    }
+
+    @Test
+    public void testIsLockscreenPublicMode() {
+        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
+        mLockscreenUserManager.setLockscreenPublicMode(true, mCurrentUser.id);
+        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
+    }
+
+    @Test
+    public void testUpdateIsPublicMode() {
+        when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
+
+        NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
+        mLockscreenUserManager.addNotificationStateChangedListener(listener);
+        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+
+        // first call explicitly sets user 0 to not public; notifies
+        mLockscreenUserManager.updatePublicMode();
+        TestableLooper.get(this).processAllMessages();
+        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+        verify(listener).onNotificationStateChanged();
+        clearInvocations(listener);
+
+        // calling again has no changes; does not notify
+        mLockscreenUserManager.updatePublicMode();
+        TestableLooper.get(this).processAllMessages();
+        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+        verify(listener, never()).onNotificationStateChanged();
+
+        // Calling again with keyguard now showing makes user 0 public; notifies
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        mLockscreenUserManager.updatePublicMode();
+        TestableLooper.get(this).processAllMessages();
+        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+        verify(listener).onNotificationStateChanged();
+        clearInvocations(listener);
+
+        // calling again has no changes; does not notify
+        mLockscreenUserManager.updatePublicMode();
+        TestableLooper.get(this).processAllMessages();
+        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+        verify(listener, never()).onNotificationStateChanged();
+    }
+
+    @Test
+    public void testDevicePolicyDoesNotAllowNotifications() {
+        // User allows them
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides notifs on lockscreen
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testDevicePolicyDoesNotAllowNotifications_secondary() {
+        Mockito.clearInvocations(mDevicePolicyManager);
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides notifications
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
+                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Test
+    public void testDevicePolicy_noPrivateNotifications() {
+        Mockito.clearInvocations(mDevicePolicyManager);
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides sensitive content
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+    }
+
+    @Test
+    public void testDevicePolicy_noPrivateNotifications_userAll() {
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides sensitive content
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder()
+                .setNotification(new Notification())
+                .setUser(UserHandle.ALL)
+                .build()));
+    }
+
+    @Test
+    public void testDevicePolicyPrivateNotifications_secondary() {
+        Mockito.clearInvocations(mDevicePolicyManager);
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides sensitive content
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
+                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+    }
+
+    @Test
+    public void testHideNotifications_primary() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testHideNotifications_secondary() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Test
+    public void testHideNotifications_secondary_userSwitch() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Test
+    public void testShowNotifications_secondary_userSwitch() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Test
+    public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
+        // DevicePolicy allows notifications
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(0);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        // KeyguardManager does not allow notifications
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no
+        // callback, so it's only updated when the setting is
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testUserAllowsNotificationsInPublic_settingsChange() {
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+
+        // User disables
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    private class TestNotificationLockscreenUserManager
+            extends NotificationLockscreenUserManagerImpl {
+        public TestNotificationLockscreenUserManager(Context context) {
+            super(
+                    context,
+                    mBroadcastDispatcher,
+                    mDevicePolicyManager,
+                    mUserManager,
+                    mUserTracker,
+                    (() -> mVisibilityProvider),
+                    (() -> mNotifCollection),
+                    mClickNotifier,
+                    (() -> mOverviewProxyService),
+                    NotificationLockscreenUserManagerMainThreadTest.this.mKeyguardManager,
+                    mStatusBarStateController,
+                    Handler.createAsync(Looper.myLooper()),
+                    Handler.createAsync(Looper.myLooper()),
+                    mBackgroundExecutor,
+                    mDeviceProvisionedController,
+                    mKeyguardStateController,
+                    mSettings,
+                    mock(DumpManager.class),
+                    mock(LockPatternUtils.class),
+                    mFakeFeatureFlags);
+        }
+
+        public BroadcastReceiver getBaseBroadcastReceiverForTest() {
+            return mBaseBroadcastReceiver;
+        }
+
+        public UserTracker.Callback getUserTrackerCallbackForTest() {
+            return mUserChangedCallback;
+        }
+
+        public ContentObserver getLockscreenSettingsObserverForTest() {
+            return mLockscreenSettingsObserver;
+        }
+
+        public ContentObserver getSettingsObserverForTest() {
+            return mSettingsObserver;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 19863ec..a5f5fc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -16,17 +16,23 @@
 
 package com.android.systemui.statusbar;
 
+import static android.app.Notification.VISIBILITY_PRIVATE;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
 import static android.os.UserHandle.USER_ALL;
+import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
 import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
 
 import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
@@ -38,12 +44,15 @@
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
+import android.app.NotificationChannel;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -59,6 +68,8 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.settings.UserTracker;
@@ -74,12 +85,16 @@
 import com.google.android.collect.Lists;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.Executor;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -121,11 +136,15 @@
     private NotificationEntry mCurrentUserNotif;
     private NotificationEntry mSecondaryUserNotif;
     private NotificationEntry mWorkProfileNotif;
+    private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
+    private Executor mBackgroundExecutor = Runnable::run; // Direct executor
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+
         int currentUserId = ActivityManager.getCurrentUser();
         when(mUserTracker.getUserId()).thenReturn(currentUserId);
         mSettings = new FakeSettings();
@@ -138,103 +157,144 @@
         when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);
         when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(
                 mCurrentUser, mWorkUser));
+        when(mUserManager.getUsers()).thenReturn(Lists.newArrayList(
+                mCurrentUser, mWorkUser, mSecondaryUser));
         when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(
                 mSecondaryUser));
         mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
                 Handler.createAsync(Looper.myLooper()));
 
         Notification notifWithPrivateVisibility = new Notification();
-        notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE;
+        notifWithPrivateVisibility.visibility = VISIBILITY_PRIVATE;
         mCurrentUserNotif = new NotificationEntryBuilder()
                 .setNotification(notifWithPrivateVisibility)
                 .setUser(new UserHandle(mCurrentUser.id))
                 .build();
+        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+        channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE);
+        mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking())
+                .setChannel(channel)
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+        when(mNotifCollection.getEntry(mCurrentUserNotif.getKey())).thenReturn(mCurrentUserNotif);
         mSecondaryUserNotif = new NotificationEntryBuilder()
                 .setNotification(notifWithPrivateVisibility)
                 .setUser(new UserHandle(mSecondaryUser.id))
                 .build();
+        mSecondaryUserNotif.setRanking(new RankingBuilder(mSecondaryUserNotif.getRanking())
+                .setChannel(channel)
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+        when(mNotifCollection.getEntry(
+                mSecondaryUserNotif.getKey())).thenReturn(mSecondaryUserNotif);
         mWorkProfileNotif = new NotificationEntryBuilder()
                 .setNotification(notifWithPrivateVisibility)
                 .setUser(new UserHandle(mWorkUser.id))
                 .build();
+        mWorkProfileNotif.setRanking(new RankingBuilder(mWorkProfileNotif.getRanking())
+                .setChannel(channel)
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+        when(mNotifCollection.getEntry(mWorkProfileNotif.getKey())).thenReturn(mWorkProfileNotif);
 
         mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
         mLockscreenUserManager.setUpWithPresenter(mPresenter);
     }
 
+    private void changeSetting(String setting) {
+        final Collection<Uri> lockScreenUris = new ArrayList<>();
+        lockScreenUris.add(Settings.Secure.getUriFor(setting));
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false,
+            lockScreenUris, 0);
+    }
+
     @Test
     public void testLockScreenShowNotificationsFalse() {
         mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
         assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
     }
 
     @Test
     public void testLockScreenShowNotificationsTrue() {
         mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
         assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
     }
 
     @Test
     public void testLockScreenAllowPrivateNotificationsTrue() {
-        mSettings.putInt(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
         assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
     }
 
     @Test
     public void testLockScreenAllowPrivateNotificationsFalse() {
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
         assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
     }
 
     @Test
     public void testLockScreenAllowsWorkPrivateNotificationsFalse() {
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mWorkUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
         assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
     }
 
     @Test
     public void testLockScreenAllowsWorkPrivateNotificationsTrue() {
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mWorkUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
         assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
     }
 
     @Test
-    public void testCurrentUserPrivateNotificationsNotRedacted() {
+    public void testCurrentUserPrivateNotificationsRedacted() {
         // GIVEN current user doesn't allow private notifications to show
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
 
         // THEN current user's notification is redacted
         assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
     }
 
     @Test
-    public void testCurrentUserPrivateNotificationsRedacted() {
+    public void testCurrentUserPrivateNotificationsNotRedacted() {
         // GIVEN current user allows private notifications to show
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
 
         // THEN current user's notification isn't redacted
         assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
     }
 
     @Test
+    public void testCurrentUserPrivateNotificationsRedactedChannel() {
+        // GIVEN current user allows private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // but doesn't allow it at the channel level
+        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+        channel.setLockscreenVisibility(VISIBILITY_PRIVATE);
+        mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking())
+                .setChannel(channel)
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+        // THEN the notification is redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+    }
+
+    @Test
     public void testWorkPrivateNotificationsRedacted() {
         // GIVEN work profile doesn't private notifications to show
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mWorkUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
 
         // THEN work profile notification is redacted
         assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
@@ -244,9 +304,9 @@
     @Test
     public void testWorkPrivateNotificationsNotRedacted() {
         // GIVEN work profile allows private notifications to show
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mWorkUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
 
         // THEN work profile notification isn't redacted
         assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
@@ -256,13 +316,15 @@
     @Test
     public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() {
         // GIVEN work profile allows private notifications to show but the other users don't
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mWorkUser.id);
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mCurrentUser.id);
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mSecondaryUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
 
         // THEN the work profile notification doesn't need to be redacted
         assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
@@ -275,13 +337,15 @@
     @Test
     public void testWorkProfileRedacted_otherUsersNotRedacted() {
         // GIVEN work profile doesn't allow private notifications to show but the other users do
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mWorkUser.id);
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mCurrentUser.id);
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mSecondaryUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
 
         // THEN the work profile notification needs to be redacted
         assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
@@ -295,11 +359,12 @@
     public void testSecondaryUserNotRedacted_currentUserRedacted() {
         // GIVEN secondary profile allows private notifications to show but the current user
         // doesn't allow private notifications to show
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mCurrentUser.id);
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mSecondaryUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
 
         // THEN the secondary profile notification still needs to be redacted because the current
         // user's setting takes precedence
@@ -323,6 +388,7 @@
     @Test
     public void testUpdateIsPublicMode() {
         when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
 
         NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
         mLockscreenUserManager.addNotificationStateChangedListener(listener);
@@ -330,24 +396,28 @@
 
         // first call explicitly sets user 0 to not public; notifies
         mLockscreenUserManager.updatePublicMode();
+        TestableLooper.get(this).processAllMessages();
         assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
         verify(listener).onNotificationStateChanged();
         clearInvocations(listener);
 
         // calling again has no changes; does not notify
         mLockscreenUserManager.updatePublicMode();
+        TestableLooper.get(this).processAllMessages();
         assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
         verify(listener, never()).onNotificationStateChanged();
 
         // Calling again with keyguard now showing makes user 0 public; notifies
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         mLockscreenUserManager.updatePublicMode();
+        TestableLooper.get(this).processAllMessages();
         assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
         verify(listener).onNotificationStateChanged();
         clearInvocations(listener);
 
         // calling again has no changes; does not notify
         mLockscreenUserManager.updatePublicMode();
+        TestableLooper.get(this).processAllMessages();
         assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
         verify(listener, never()).onNotificationStateChanged();
     }
@@ -356,14 +426,14 @@
     public void testDevicePolicyDoesNotAllowNotifications() {
         // User allows them
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         // DevicePolicy hides notifs on lockscreen
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
 
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
         mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
         mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
                 new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
@@ -371,19 +441,18 @@
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
     }
 
-    @Ignore("b/286230167")
     @Test
     public void testDevicePolicyDoesNotAllowNotifications_userAll() {
         // User allows them
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         // DevicePolicy hides notifications
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
 
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
         mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
         mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
                 new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
@@ -392,98 +461,105 @@
     }
 
     @Test
-    @Ignore("b/286230167")
     public void testDevicePolicyDoesNotAllowNotifications_secondary() {
+        Mockito.clearInvocations(mDevicePolicyManager);
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         // DevicePolicy hides notifications
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
                 .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
 
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
         mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
         mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
                 new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
 
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
 
-        // TODO (b/286230167): enable assertion
         verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
     }
 
     @Test
     public void testDevicePolicy_noPrivateNotifications() {
+        Mockito.clearInvocations(mDevicePolicyManager);
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         // DevicePolicy hides sensitive content
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
 
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
         mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
         mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
                 new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
 
         assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
 
-        // TODO (b/286230167): enable assertion. It's currently called 4 times.
-        //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
     }
 
     @Test
     public void testDevicePolicy_noPrivateNotifications_userAll() {
+        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+        channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE);
+        NotificationEntry notifEntry = new NotificationEntryBuilder()
+                .setNotification(new Notification())
+                .setUser(UserHandle.ALL)
+                .build();
+        notifEntry.setRanking(new RankingBuilder(notifEntry.getRanking())
+                .setChannel(channel)
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+        when(mNotifCollection.getEntry(notifEntry.getKey())).thenReturn(notifEntry);
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         // DevicePolicy hides sensitive content
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
 
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
         mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
         mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
                 new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
 
-        assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder()
-                .setNotification(new Notification())
-                .setUser(UserHandle.ALL)
-                .build()));
+        assertTrue(mLockscreenUserManager.needsRedaction(notifEntry));
     }
 
     @Test
     public void testDevicePolicyPrivateNotifications_secondary() {
+        Mockito.clearInvocations(mDevicePolicyManager);
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         // DevicePolicy hides sensitive content
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
                 .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
 
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
         mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
         mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
                 new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
 
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
         assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
 
-        // TODO (b/286230167): enable assertion. It's currently called 5 times.
-        //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
     }
 
     @Test
     public void testHideNotifications_primary() {
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
     }
@@ -491,16 +567,17 @@
     @Test
     public void testHideNotifications_secondary() {
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
     }
 
-    @Ignore("b/286230167")
     @Test
     public void testHideNotifications_workProfile() {
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mWorkUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mWorkUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mWorkUser.id));
     }
@@ -508,67 +585,62 @@
     @Test
     public void testHideNotifications_secondary_userSwitch() {
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
-        TestNotificationLockscreenUserManager lockscreenUserManager
-                = new TestNotificationLockscreenUserManager(mContext);
-        lockscreenUserManager.setUpWithPresenter(mPresenter);
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
 
-        lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
-        assertFalse(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
     }
 
     @Test
     public void testShowNotifications_secondary_userSwitch() {
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
-        TestNotificationLockscreenUserManager lockscreenUserManager
-                = new TestNotificationLockscreenUserManager(mContext);
-        lockscreenUserManager.setUpWithPresenter(mPresenter);
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
 
-        lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
-        assertTrue(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
     }
 
-    @Ignore("b/286230167")
     @Test
     public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications() {
+        // KeyguardManager does not allow notifications
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
         // DevicePolicy allows notifications
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(0);
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
         mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
         mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
                 new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
 
-        // KeyguardManager does not
-        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
-
         assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
     }
 
     @Test
     public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
         // DevicePolicy allows notifications
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(0);
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
         mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
         mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
                 new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
 
-        // KeyguardManager does not
+        // KeyguardManager does not allow notifications
         when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
 
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no
+        // callback, so it's only updated when the setting is
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
     }
 
@@ -576,31 +648,30 @@
     public void testUserAllowsNotificationsInPublic_settingsChange() {
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
 
         // User disables
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
     }
 
-    @Ignore("b/286230167")
     @Test
     public void testUserAllowsNotificationsInPublic_devicePolicyChange() {
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
 
         // DevicePolicy disables notifications
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
         mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
         mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
                 new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
@@ -608,6 +679,28 @@
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
     }
 
+    @Test
+    public void testNewUserAdded() {
+        int newUserId = 14;
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, newUserId);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, newUserId);
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, newUserId))
+                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+        BroadcastReceiver broadcastReceiver =
+                mLockscreenUserManager.getBaseBroadcastReceiverForTest();
+        final Bundle extras = new Bundle();
+        final Intent intent = new Intent(Intent.ACTION_USER_ADDED);
+        intent.putExtras(extras);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
+        broadcastReceiver.onReceive(mContext, intent);
+
+        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), eq(newUserId));
+
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(newUserId));
+        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(newUserId));
+    }
+
     private class TestNotificationLockscreenUserManager
             extends NotificationLockscreenUserManagerImpl {
         public TestNotificationLockscreenUserManager(Context context) {
@@ -623,12 +716,17 @@
                     (() -> mOverviewProxyService),
                     NotificationLockscreenUserManagerTest.this.mKeyguardManager,
                     mStatusBarStateController,
-                    Handler.createAsync(Looper.myLooper()),
+                    Handler.createAsync(TestableLooper.get(
+                            NotificationLockscreenUserManagerTest.this).getLooper()),
+                    Handler.createAsync(TestableLooper.get(
+                            NotificationLockscreenUserManagerTest.this).getLooper()),
+                    mBackgroundExecutor,
                     mDeviceProvisionedController,
                     mKeyguardStateController,
                     mSettings,
                     mock(DumpManager.class),
-                    mock(LockPatternUtils.class));
+                    mock(LockPatternUtils.class),
+                    mFakeFeatureFlags);
         }
 
         public BroadcastReceiver getBaseBroadcastReceiverForTest() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index 83f2a5d..b922ab3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -22,6 +22,7 @@
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
 
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -36,6 +37,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.NotificationChannel;
 import android.content.Context;
 import android.os.Handler;
 import android.os.UserHandle;
@@ -51,6 +53,9 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -96,6 +101,7 @@
     @Mock private UserTracker mUserTracker;
     private final FakeSettings mSecureSettings = new FakeSettings();
     private final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
+    private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
 
     private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
     private NotificationEntry mEntry;
@@ -116,7 +122,8 @@
                                 mStatusBarStateController,
                                 mUserTracker,
                                 mSecureSettings,
-                                mGlobalSettings);
+                                mGlobalSettings,
+                                mFeatureFlags);
         mKeyguardNotificationVisibilityProvider = component.getProvider();
         for (CoreStartable startable : component.getCoreStartables().values()) {
             startable.start();
@@ -424,6 +431,7 @@
 
     @Test
     public void publicMode_settingsDisallow() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
         // GIVEN an 'unfiltered-keyguard-showing' state
         setupUnfilteredState(mEntry);
 
@@ -433,12 +441,59 @@
         when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))
                 .thenReturn(false);
 
+        mEntry.setRanking(new RankingBuilder()
+                .setChannel(new NotificationChannel("1", "1", 4))
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE)
+                .setKey(mEntry.getKey()).build());
+
+        // THEN filter out the entry
+        assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
+    public void publicMode_settingsDisallow_mainThread() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
+        // GIVEN an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState(mEntry);
+
+        // WHEN the notification's user is in public mode and settings are configured to disallow
+        // notifications in public mode
+        when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true);
+        when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))
+                .thenReturn(false);
+
+        mEntry.setRanking(new RankingBuilder()
+                .setChannel(new NotificationChannel("1", "1", 4))
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE)
+                .setKey(mEntry.getKey()).build());
+
         // THEN filter out the entry
         assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
     }
 
     @Test
     public void publicMode_notifDisallowed() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+        channel.setLockscreenVisibility(VISIBILITY_SECRET);
+        // GIVEN an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState(mEntry);
+
+        // WHEN the notification's user is in public mode and settings are configured to disallow
+        // notifications in public mode
+        when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true);
+        mEntry.setRanking(new RankingBuilder()
+                .setKey(mEntry.getKey())
+                .setChannel(channel)
+                .setVisibilityOverride(VISIBILITY_SECRET).build());
+
+        // THEN filter out the entry
+        assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
+    public void publicMode_notifDisallowed_mainThread() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
         // GIVEN an 'unfiltered-keyguard-showing' state
         setupUnfilteredState(mEntry);
 
@@ -506,6 +561,54 @@
     }
 
     @Test
+    public void notificationChannelVisibilityNoOverride() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+        // GIVEN a VISIBILITY_PRIVATE notification
+        NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID));
+        entryBuilder.modifyNotification(mContext)
+                .setVisibility(VISIBILITY_PRIVATE);
+        mEntry = entryBuilder.build();
+        // ranking says secret because of DPC or Setting
+        mEntry.setRanking(new RankingBuilder()
+                .setKey(mEntry.getKey())
+                .setVisibilityOverride(VISIBILITY_SECRET)
+                .setImportance(IMPORTANCE_HIGH)
+                .build());
+
+        // WHEN we're in an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState(mEntry);
+
+        // THEN don't hide the entry based on visibility. (Redaction is handled elsewhere.)
+        assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
+    public void notificationChannelVisibilitySecret() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+        // GIVEN a VISIBILITY_PRIVATE notification
+        NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID));
+        entryBuilder.modifyNotification(mContext)
+                .setVisibility(VISIBILITY_PRIVATE);
+        // And a VISIBILITY_SECRET NotificationChannel
+        NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_HIGH);
+        channel.setLockscreenVisibility(VISIBILITY_SECRET);
+        mEntry = entryBuilder.build();
+        // WHEN we're in an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState(mEntry);
+        when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true);
+        when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true);
+
+        mEntry.setRanking(new RankingBuilder(mEntry.getRanking())
+                .setChannel(channel)
+                .build());
+
+        // THEN hide the entry based on visibility.
+        assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
     public void notificationVisibilityPrivate() {
         // GIVEN a VISIBILITY_PRIVATE notification
         NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
@@ -635,7 +738,8 @@
                     @BindsInstance SysuiStatusBarStateController statusBarStateController,
                     @BindsInstance UserTracker userTracker,
                     @BindsInstance SecureSettings secureSettings,
-                    @BindsInstance GlobalSettings globalSettings
+                    @BindsInstance GlobalSettings globalSettings,
+                    @BindsInstance FeatureFlagsClassic featureFlags
             );
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 03f5f00..3556703 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -30,9 +30,11 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.doze.util.BurnInHelperKt;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.core.FakeLogBuffer;
+import com.android.systemui.res.R;
 
 import org.junit.After;
 import org.junit.Before;
@@ -51,8 +53,7 @@
     private static final float OPAQUE = 1.f;
     private static final float TRANSPARENT = 0.f;
 
-    @Mock
-    private Resources mResources;
+    @Mock private Resources mResources;
 
     private KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
     private KeyguardClockPositionAlgorithm.Result mClockPosition;
@@ -80,7 +81,8 @@
                 .mockStatic(BurnInHelperKt.class)
                 .startMocking();
 
-        mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm();
+        LogBuffer logBuffer = FakeLogBuffer.Factory.Companion.create();
+        mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm(logBuffer);
         when(mResources.getDimensionPixelSize(anyInt())).thenReturn(0);
         mClockPositionAlgorithm.loadDimens(mResources);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 812e91b..610fede 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -60,6 +60,8 @@
 
     override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
 
+    override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
+
     fun setDataEnabled(enabled: Boolean) {
         _dataEnabled.value = enabled
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index 57f97ec..1f8cc54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -123,6 +123,7 @@
                     carrierNetworkChange = testCase.carrierNetworkChange,
                     roaming = testCase.roaming,
                     name = "demo name",
+                    slice = testCase.slice,
                 )
 
             fakeNetworkEventFlow.value = networkModel
@@ -142,6 +143,7 @@
             launch { conn.carrierName.collect {} }
             launch { conn.isEmergencyOnly.collect {} }
             launch { conn.dataConnectionState.collect {} }
+            launch { conn.hasPrioritizedNetworkCapabilities.collect {} }
         }
         return job
     }
@@ -165,6 +167,7 @@
                     .isEqualTo(NetworkNameModel.IntentDerived(model.name))
                 assertThat(conn.carrierName.value)
                     .isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}"))
+                assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice)
 
                 // TODO(b/261029387): check these once we start handling them
                 assertThat(conn.isEmergencyOnly.value).isFalse()
@@ -190,6 +193,7 @@
         val carrierNetworkChange: Boolean,
         val roaming: Boolean,
         val name: String,
+        val slice: Boolean,
     ) {
         override fun toString(): String {
             return "INPUT(level=$level, " +
@@ -200,7 +204,8 @@
                 "activity=$activity, " +
                 "carrierNetworkChange=$carrierNetworkChange, " +
                 "roaming=$roaming, " +
-                "name=$name)"
+                "name=$name," +
+                "slice=$slice)"
         }
 
         // Convenience for iterating test data and creating new cases
@@ -214,6 +219,7 @@
             carrierNetworkChange: Boolean? = null,
             roaming: Boolean? = null,
             name: String? = null,
+            slice: Boolean? = null,
         ): TestCase =
             TestCase(
                 level = level ?: this.level,
@@ -225,6 +231,7 @@
                 carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange,
                 roaming = roaming ?: this.roaming,
                 name = name ?: this.name,
+                slice = slice ?: this.slice,
             )
     }
 
@@ -254,6 +261,7 @@
         // false first so the base case doesn't have roaming set (more common)
         private val roaming = listOf(false, true)
         private val names = listOf("name 1", "name 2")
+        private val slice = listOf(false, true)
 
         @Parameters(name = "{0}") @JvmStatic fun data() = testData()
 
@@ -291,6 +299,7 @@
                     carrierNetworkChange.first(),
                     roaming.first(),
                     names.first(),
+                    slice.first(),
                 )
 
             val tail =
@@ -303,6 +312,7 @@
                         carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) },
                         roaming.map { baseCase.modifiedBy(roaming = it) },
                         names.map { baseCase.modifiedBy(name = it) },
+                        slice.map { baseCase.modifiedBy(slice = it) }
                     )
                     .flatten()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index 2712b70..d918fa8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -549,6 +549,7 @@
             launch { conn.carrierName.collect {} }
             launch { conn.isEmergencyOnly.collect {} }
             launch { conn.dataConnectionState.collect {} }
+            launch { conn.hasPrioritizedNetworkCapabilities.collect {} }
         }
         return job
     }
@@ -574,6 +575,7 @@
                     .isEqualTo(NetworkNameModel.IntentDerived(model.name))
                 assertThat(conn.carrierName.value)
                     .isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}"))
+                assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice)
 
                 // TODO(b/261029387) check these once we start handling them
                 assertThat(conn.isEmergencyOnly.value).isFalse()
@@ -599,6 +601,7 @@
         assertThat(conn.isEmergencyOnly.value).isFalse()
         assertThat(conn.isGsm.value).isFalse()
         assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected)
+        assertThat(conn.hasPrioritizedNetworkCapabilities.value).isFalse()
         job.cancel()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index ede02d1..1c21ebe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
 
+import android.net.ConnectivityManager
 import android.telephony.ServiceState
 import android.telephony.SignalStrength
 import android.telephony.TelephonyCallback
@@ -80,6 +81,7 @@
         )
     private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>()
     private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>()
+    private val connectivityManager = mock<ConnectivityManager>()
 
     private val subscriptionModel =
         MutableStateFlow(
@@ -678,6 +680,7 @@
                 subscriptionModel,
                 DEFAULT_NAME_MODEL,
                 SEP,
+                connectivityManager,
                 telephonyManager,
                 systemUiCarrierConfig = mock(),
                 fakeBroadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 8ef82c9..ba64265 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -19,6 +19,8 @@
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
 import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN
 import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN
 import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
@@ -85,7 +87,9 @@
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -107,6 +111,7 @@
     private lateinit var underTest: MobileConnectionRepositoryImpl
     private lateinit var connectionsRepo: FakeMobileConnectionsRepository
 
+    @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: MobileInputLogger
     @Mock private lateinit var tableLogger: TableLogBuffer
@@ -144,6 +149,7 @@
                 subscriptionModel,
                 DEFAULT_NAME_MODEL,
                 SEP,
+                connectivityManager,
                 telephonyManager,
                 systemUiCarrierConfig,
                 fakeBroadcastDispatcher,
@@ -904,6 +910,45 @@
             assertThat(latest).isFalse()
         }
 
+    @Test
+    fun hasPrioritizedCaps_defaultFalse() {
+        assertThat(underTest.hasPrioritizedNetworkCapabilities.value).isFalse()
+    }
+
+    @Test
+    fun hasPrioritizedCaps_trueWhenAvailable() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.hasPrioritizedNetworkCapabilities)
+
+            val callback: NetworkCallback =
+                withArgCaptor<NetworkCallback> {
+                    verify(connectivityManager).registerNetworkCallback(any(), capture())
+                }
+
+            callback.onAvailable(mock())
+
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun hasPrioritizedCaps_becomesFalseWhenNetworkLost() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.hasPrioritizedNetworkCapabilities)
+
+            val callback: NetworkCallback =
+                withArgCaptor<NetworkCallback> {
+                    verify(connectivityManager).registerNetworkCallback(any(), capture())
+                }
+
+            callback.onAvailable(mock())
+
+            assertThat(latest).isTrue()
+
+            callback.onLost(mock())
+
+            assertThat(latest).isFalse()
+        }
+
     private inline fun <reified T> getTelephonyCallbackForType(): T {
         return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index 852ed20..889f60a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
 
+import android.net.ConnectivityManager
 import android.telephony.ServiceState
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.CarrierNetworkListener
@@ -96,6 +97,7 @@
     private lateinit var underTest: MobileConnectionRepositoryImpl
     private lateinit var connectionsRepo: FakeMobileConnectionsRepository
 
+    @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: MobileInputLogger
     @Mock private lateinit var tableLogger: TableLogBuffer
@@ -129,6 +131,7 @@
                 subscriptionModel,
                 DEFAULT_NAME,
                 SEP,
+                connectivityManager,
                 telephonyManager,
                 systemUiCarrierConfig,
                 fakeBroadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 9148c75..18ba6c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -180,6 +180,7 @@
             MobileConnectionRepositoryImpl.Factory(
                 context,
                 fakeBroadcastDispatcher,
+                connectivityManager,
                 telephonyManager = telephonyManager,
                 bgDispatcher = dispatcher,
                 logger = logger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index de2b6a8..5f4d7bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -48,6 +48,8 @@
             NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
         )
 
+    override val showSliceAttribution = MutableStateFlow(false)
+
     override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo mode"))
 
     override val carrierName = MutableStateFlow("demo mode")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
index 218fd33..3936bed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
@@ -22,13 +22,15 @@
 import android.testing.TestableLooper.RunWithLooper
 import android.testing.ViewUtils
 import android.view.View
+import android.widget.FrameLayout
 import android.widget.ImageView
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
@@ -58,8 +60,8 @@
     private lateinit var testableLooper: TestableLooper
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
-    @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock private lateinit var viewLogger: MobileViewLogger
     @Mock private lateinit var constants: ConnectivityConstants
@@ -246,7 +248,8 @@
         testableLooper.processAllMessages()
 
         val color = 0x12345678
-        view.onDarkChanged(arrayListOf(), 1.0f, color)
+        val contrast = 0x12344321
+        view.onDarkChangedWithContrast(arrayListOf(), color, contrast)
         testableLooper.processAllMessages()
 
         assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
@@ -267,7 +270,8 @@
         testableLooper.processAllMessages()
 
         val color = 0x23456789
-        view.setStaticDrawableColor(color)
+        val contrast = 0x12344321
+        view.setStaticDrawableColor(color, contrast)
         testableLooper.processAllMessages()
 
         assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
@@ -275,6 +279,35 @@
         ViewUtils.detachView(view)
     }
 
+    @Test
+    fun colorChange_layersUpdateWithContrast() {
+        // Allow the slice, and set it to visible. This cause us to use special color logic
+        flags.set(Flags.NEW_NETWORK_SLICE_UI, true)
+        interactor.showSliceAttribution.value = true
+        createViewModel()
+
+        val view =
+            ModernStatusBarMobileView.constructAndBind(
+                context,
+                viewLogger,
+                SLOT_NAME,
+                viewModel,
+            )
+        ViewUtils.attachView(view)
+        testableLooper.processAllMessages()
+
+        val color = 0x23456789
+        val contrast = 0x12344321
+        view.setStaticDrawableColor(color, contrast)
+
+        testableLooper.processAllMessages()
+
+        assertThat(view.getNetTypeContainer().backgroundTintList).isEqualTo(color.colorState())
+        assertThat(view.getNetTypeView().imageTintList).isEqualTo(contrast.colorState())
+
+        ViewUtils.detachView(view)
+    }
+
     private fun View.getGroupView(): View {
         return this.requireViewById(R.id.mobile_group)
     }
@@ -283,10 +316,20 @@
         return this.requireViewById(R.id.mobile_signal)
     }
 
+    private fun View.getNetTypeContainer(): FrameLayout {
+        return this.requireViewById(R.id.mobile_type_container)
+    }
+
+    private fun View.getNetTypeView(): ImageView {
+        return this.requireViewById(R.id.mobile_type)
+    }
+
     private fun View.getDotView(): View {
         return this.requireViewById(R.id.status_bar_dot)
     }
 
+    private fun Int.colorState() = ColorStateList.valueOf(this)
+
     private fun createViewModel() {
         viewModelCommon =
             MobileIconViewModel(
@@ -294,6 +337,7 @@
                 interactor,
                 airplaneModeInteractor,
                 constants,
+                flags,
                 testScope.backgroundScope,
             )
         viewModel = QsMobileIconViewModel(viewModelCommon)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index 1878329..1d5487f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -16,8 +16,11 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
@@ -47,12 +50,14 @@
 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
 
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RunWith(AndroidJUnit4::class)
 class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
     private lateinit var commonImpl: MobileIconViewModelCommon
     private lateinit var homeIcon: HomeMobileIconViewModel
@@ -65,6 +70,7 @@
     private lateinit var airplaneModeInteractor: AirplaneModeInteractor
 
     private val connectivityRepository = FakeConnectivityRepository()
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
     @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var constants: ConnectivityConstants
@@ -133,6 +139,7 @@
                 interactor,
                 airplaneModeInteractor,
                 constants,
+                flags,
                 testScope.backgroundScope,
             )
 
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 796d5ec..c831e62 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,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
 import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
@@ -26,7 +27,12 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.Flags.NEW_NETWORK_SLICE_UI
 import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -54,12 +60,14 @@
 import kotlinx.coroutines.yield
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RunWith(AndroidJUnit4::class)
 class MobileIconViewModelTest : SysuiTestCase() {
     private var connectivityRepository = FakeConnectivityRepository()
 
@@ -74,6 +82,7 @@
     @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
 
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
@@ -595,6 +604,46 @@
             containerJob.cancel()
         }
 
+    @Test
+    fun netTypeBackground_flagOff_isNull() =
+        testScope.runTest {
+            flags.set(NEW_NETWORK_SLICE_UI, false)
+            createAndSetViewModel()
+
+            val latest by collectLastValue(underTest.networkTypeBackground)
+
+            repository.hasPrioritizedNetworkCapabilities.value = true
+
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun netTypeBackground_flagOn_nullWhenNoPrioritizedCapabilities() =
+        testScope.runTest {
+            flags.set(NEW_NETWORK_SLICE_UI, true)
+            createAndSetViewModel()
+
+            val latest by collectLastValue(underTest.networkTypeBackground)
+
+            repository.hasPrioritizedNetworkCapabilities.value = false
+
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun netTypeBackground_flagOn_notNullWhenPrioritizedCapabilities() =
+        testScope.runTest {
+            flags.set(NEW_NETWORK_SLICE_UI, true)
+            createAndSetViewModel()
+
+            val latest by collectLastValue(underTest.networkTypeBackground)
+
+            repository.hasPrioritizedNetworkCapabilities.value = true
+
+            assertThat(latest)
+                .isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background, null))
+        }
+
     private fun createAndSetViewModel() {
         underTest =
             MobileIconViewModel(
@@ -602,6 +651,7 @@
                 interactor,
                 airplaneModeInteractor,
                 constants,
+                flags,
                 testScope.backgroundScope,
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index eb6f2f8..f3e334e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -16,9 +16,12 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
@@ -41,15 +44,18 @@
 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
 
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RunWith(AndroidJUnit4::class)
 class MobileIconsViewModelTest : SysuiTestCase() {
     private lateinit var underTest: MobileIconsViewModel
     private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
     private lateinit var airplaneModeInteractor: AirplaneModeInteractor
     @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@@ -77,6 +83,7 @@
                 interactor,
                 airplaneModeInteractor,
                 constants,
+                flags,
                 testScope.backgroundScope,
             )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
index e44ff8e..28d632d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
@@ -41,6 +41,7 @@
      * setting mobile connected && validated, since the default state is disconnected && not
      * validated
      */
+    @JvmOverloads
     fun setMobileConnected(
         default: Boolean = true,
         validated: Boolean = true,
@@ -53,6 +54,7 @@
     }
 
     /** Similar convenience method for ethernet */
+    @JvmOverloads
     fun setEthernetConnected(
         default: Boolean = true,
         validated: Boolean = true,
@@ -64,6 +66,7 @@
             )
     }
 
+    @JvmOverloads
     fun setWifiConnected(
         default: Boolean = true,
         validated: Boolean = true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
index b4039d9..028a58c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
@@ -77,9 +77,10 @@
     fun onDarkChanged_bindingReceivesIconAndDecorTint() {
         val view = createAndInitView()
 
-        view.onDarkChanged(arrayListOf(), 1.0f, 0x12345678)
+        view.onDarkChangedWithContrast(arrayListOf(), 0x12345678, 0x12344321)
 
         assertThat(binding.iconTint).isEqualTo(0x12345678)
+        assertThat(binding.contrastTint).isEqualTo(0x12344321)
         assertThat(binding.decorTint).isEqualTo(0x12345678)
     }
 
@@ -87,9 +88,10 @@
     fun setStaticDrawableColor_bindingReceivesIconTint() {
         val view = createAndInitView()
 
-        view.setStaticDrawableColor(0x12345678)
+        view.setStaticDrawableColor(0x12345678, 0x12344321)
 
         assertThat(binding.iconTint).isEqualTo(0x12345678)
+        assertThat(binding.contrastTint).isEqualTo(0x12344321)
     }
 
     @Test
@@ -144,13 +146,15 @@
 
     inner class TestBinding : ModernStatusBarViewBinding {
         var iconTint: Int? = null
+        var contrastTint: Int? = null
         var decorTint: Int? = null
         var onVisibilityStateChangedCalled: Boolean = false
 
         var shouldIconBeVisibleInternal: Boolean = true
 
-        override fun onIconTintChanged(newTint: Int) {
+        override fun onIconTintChanged(newTint: Int, contrastTint: Int) {
             iconTint = newTint
+            this.contrastTint = contrastTint
         }
 
         override fun onDecorTintChanged(newTint: Int) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index a27f899..d75a452 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -25,9 +25,9 @@
 import android.view.ViewGroup
 import android.widget.ImageView
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
@@ -229,7 +229,8 @@
         testableLooper.processAllMessages()
 
         val color = 0x12345678
-        view.onDarkChanged(arrayListOf(), 1.0f, color)
+        val contrast = 0x12344321
+        view.onDarkChangedWithContrast(arrayListOf(), color, contrast)
         testableLooper.processAllMessages()
 
         assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
@@ -244,7 +245,8 @@
         testableLooper.processAllMessages()
 
         val color = 0x23456789
-        view.setStaticDrawableColor(color)
+        val contrast = 0x12344321
+        view.setStaticDrawableColor(color, contrast)
         testableLooper.processAllMessages()
 
         assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 10ac2eb..f09cb19 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -48,3 +48,17 @@
     description: "Stops using the deprecated PackageListObserver."
     bug: "304561459"
 }
+
+flag {
+    name: "scan_packages_without_lock"
+    namespace: "accessibility"
+    description: "Scans packages for accessibility service/activity info without holding the A11yMS lock"
+    bug: "295969873"
+}
+
+flag {
+    name: "reduce_touch_exploration_sensitivity"
+    namespace: "accessibility"
+    description: "Reduces touch exploration sensitivity by only sending a hover event when the ifnger has moved the amount of pixels defined by the system's touch slop."
+    bug: "303677860"
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index aa6d800..8c1d444 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -290,14 +290,13 @@
 
     private final Set<ComponentName> mTempComponentNameSet = new HashSet<>();
 
-    private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList =
-            new ArrayList<>();
-
     private final IntArray mTempIntArray = new IntArray(0);
 
     private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients =
             new RemoteCallbackList<>();
 
+    private PackageMonitor mPackageMonitor;
+
     @VisibleForTesting
     final SparseArray<AccessibilityUserState> mUserStates = new SparseArray<>();
 
@@ -531,6 +530,19 @@
         disableAccessibilityMenuToMigrateIfNeeded();
     }
 
+    /**
+     * Returns if the current thread is holding {@link #mLock}. Used for testing
+     * deadlock bug fixes.
+     *
+     * <p><strong>Warning:</strong> this should not be used for production logic
+     * because by the time you receive an answer it may no longer be valid.
+     * </p>
+     */
+    @VisibleForTesting
+    boolean unsafeIsLockHeld() {
+        return Thread.holdsLock(mLock);
+    }
+
     @Override
     public int getCurrentUserIdLocked() {
         return mCurrentUserId;
@@ -690,6 +702,19 @@
         }
     }
 
+    private void onSomePackagesChangedLocked(
+            @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos,
+            @Nullable List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
+        final AccessibilityUserState userState = getCurrentUserStateLocked();
+        // Reload the installed services since some services may have different attributes
+        // or resolve info (does not support equals), etc. Remove them then to force reload.
+        userState.mInstalledServices.clear();
+        if (readConfigurationForUserStateLocked(userState,
+                    parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos)) {
+            onUserStateChangedLocked(userState);
+        }
+    }
+
     private void onPackageRemovedLocked(String packageName) {
         final AccessibilityUserState userState = getCurrentUserState();
         final Predicate<ComponentName> filter =
@@ -721,8 +746,13 @@
         }
     }
 
+    @VisibleForTesting
+    PackageMonitor getPackageMonitor() {
+        return mPackageMonitor;
+    }
+
     private void registerBroadcastReceivers() {
-        PackageMonitor monitor = new PackageMonitor() {
+        mPackageMonitor = new PackageMonitor() {
             @Override
             public void onSomePackagesChanged() {
                 if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
@@ -730,13 +760,25 @@
                             FLAGS_PACKAGE_BROADCAST_RECEIVER);
                 }
 
+                final int userId = getChangingUserId();
+                List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
+                List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
+                if (Flags.scanPackagesWithoutLock()) {
+                    parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+                    parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
+                }
                 synchronized (mLock) {
                     // Only the profile parent can install accessibility services.
                     // Therefore we ignore packages from linked profiles.
-                    if (getChangingUserId() != mCurrentUserId) {
+                    if (userId != mCurrentUserId) {
                         return;
                     }
-                    onSomePackagesChangedLocked();
+                    if (Flags.scanPackagesWithoutLock()) {
+                        onSomePackagesChangedLocked(parsedAccessibilityServiceInfos,
+                                parsedAccessibilityShortcutInfos);
+                    } else {
+                        onSomePackagesChangedLocked();
+                    }
                 }
             }
 
@@ -751,8 +793,14 @@
                             FLAGS_PACKAGE_BROADCAST_RECEIVER,
                             "packageName=" + packageName + ";uid=" + uid);
                 }
+                final int userId = getChangingUserId();
+                List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
+                List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
+                if (Flags.scanPackagesWithoutLock()) {
+                    parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+                    parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
+                }
                 synchronized (mLock) {
-                    final int userId = getChangingUserId();
                     if (userId != mCurrentUserId) {
                         return;
                     }
@@ -765,8 +813,13 @@
                     // Reloads the installed services info to make sure the rebound service could
                     // get a new one.
                     userState.mInstalledServices.clear();
-                    final boolean configurationChanged =
-                            readConfigurationForUserStateLocked(userState);
+                    final boolean configurationChanged;
+                    if (Flags.scanPackagesWithoutLock()) {
+                        configurationChanged = readConfigurationForUserStateLocked(userState,
+                                parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
+                    } else {
+                        configurationChanged = readConfigurationForUserStateLocked(userState);
+                    }
                     if (reboundAService || configurationChanged) {
                         onUserStateChangedLocked(userState);
                     }
@@ -839,7 +892,7 @@
         };
 
         // package changes
-        monitor.register(mContext, null,  UserHandle.ALL, true);
+        mPackageMonitor.register(mContext, null,  UserHandle.ALL, true);
 
         if (!Flags.deprecatePackageListObserver()) {
             final PackageManagerInternal pm = LocalServices.getService(
@@ -1831,8 +1884,15 @@
         mA11yWindowManager.onTouchInteractionEnd();
     }
 
-    private void switchUser(int userId) {
+    @VisibleForTesting
+    void switchUser(int userId) {
         mMagnificationController.updateUserIdIfNeeded(userId);
+        List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
+        List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
+        if (Flags.scanPackagesWithoutLock()) {
+            parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+            parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
+        }
         synchronized (mLock) {
             if (mCurrentUserId == userId && mInitialized) {
                 return;
@@ -1857,7 +1917,12 @@
             mCurrentUserId = userId;
             AccessibilityUserState userState = getCurrentUserStateLocked();
 
-            readConfigurationForUserStateLocked(userState);
+            if (Flags.scanPackagesWithoutLock()) {
+                readConfigurationForUserStateLocked(userState,
+                        parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
+            } else {
+                readConfigurationForUserStateLocked(userState);
+            }
             mSecurityPolicy.onSwitchUserLocked(mCurrentUserId, userState.mEnabledServices);
             // Even if reading did not yield change, we have to update
             // the state since the context in which the current user
@@ -2105,8 +2170,17 @@
         }
     }
 
-    private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState) {
-        mTempAccessibilityServiceInfoList.clear();
+    /**
+     * Finds packages that provide AccessibilityService interfaces, and parses
+     * their metadata XML to build up {@link AccessibilityServiceInfo} objects.
+     *
+     * <p>
+     * <strong>Note:</strong> XML parsing is a resource-heavy operation that may
+     * stall, so this method should not be called while holding a lock.
+     * </p>
+     */
+    private List<AccessibilityServiceInfo> parseAccessibilityServiceInfos(int userId) {
+        List<AccessibilityServiceInfo> result = new ArrayList<>();
 
         int flags = PackageManager.GET_SERVICES
                 | PackageManager.GET_META_DATA
@@ -2114,12 +2188,14 @@
                 | PackageManager.MATCH_DIRECT_BOOT_AWARE
                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 
-        if (userState.getBindInstantServiceAllowedLocked()) {
-            flags |= PackageManager.MATCH_INSTANT;
+        synchronized (mLock) {
+            if (getUserStateLocked(userId).getBindInstantServiceAllowedLocked()) {
+                flags |= PackageManager.MATCH_INSTANT;
+            }
         }
 
         List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser(
-                new Intent(AccessibilityService.SERVICE_INTERFACE), flags, mCurrentUserId);
+                new Intent(AccessibilityService.SERVICE_INTERFACE), flags, userId);
 
         for (int i = 0, count = installedServices.size(); i < count; i++) {
             ResolveInfo resolveInfo = installedServices.get(i);
@@ -2132,40 +2208,60 @@
             AccessibilityServiceInfo accessibilityServiceInfo;
             try {
                 accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext);
-                if (!accessibilityServiceInfo.isWithinParcelableSize()) {
-                    Slog.e(LOG_TAG, "Skipping service "
-                            + accessibilityServiceInfo.getResolveInfo().getComponentInfo()
-                            + " because service info size is larger than safe parcelable limits.");
-                    continue;
-                }
-                if (userState.mCrashedServices.contains(serviceInfo.getComponentName())) {
-                    // Restore the crashed attribute.
-                    accessibilityServiceInfo.crashed = true;
-                }
-                mTempAccessibilityServiceInfoList.add(accessibilityServiceInfo);
             } catch (XmlPullParserException | IOException xppe) {
                 Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe);
+                continue;
+            }
+            if (!accessibilityServiceInfo.isWithinParcelableSize()) {
+                Slog.e(LOG_TAG, "Skipping service "
+                        + accessibilityServiceInfo.getResolveInfo().getComponentInfo()
+                        + " because service info size is larger than safe parcelable limits.");
+                continue;
+            }
+            result.add(accessibilityServiceInfo);
+        }
+        return result;
+    }
+
+    private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState,
+            @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos) {
+        for (int i = 0, count = parsedAccessibilityServiceInfos.size(); i < count; i++) {
+            AccessibilityServiceInfo accessibilityServiceInfo =
+                    parsedAccessibilityServiceInfos.get(i);
+            if (userState.mCrashedServices.contains(accessibilityServiceInfo.getComponentName())) {
+                // Restore the crashed attribute.
+                accessibilityServiceInfo.crashed = true;
             }
         }
 
-        if (!mTempAccessibilityServiceInfoList.equals(userState.mInstalledServices)) {
+        if (!parsedAccessibilityServiceInfos.equals(userState.mInstalledServices)) {
             userState.mInstalledServices.clear();
-            userState.mInstalledServices.addAll(mTempAccessibilityServiceInfoList);
-            mTempAccessibilityServiceInfoList.clear();
+            userState.mInstalledServices.addAll(parsedAccessibilityServiceInfos);
             return true;
         }
-
-        mTempAccessibilityServiceInfoList.clear();
         return false;
     }
 
-    private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState) {
-        final List<AccessibilityShortcutInfo> shortcutInfos = AccessibilityManager
-                .getInstance(mContext).getInstalledAccessibilityShortcutListAsUser(
-                        mContext, mCurrentUserId);
-        if (!shortcutInfos.equals(userState.mInstalledShortcuts)) {
+    /**
+     * Returns the {@link AccessibilityShortcutInfo}s of the installed
+     * accessibility shortcut targets for the given user.
+     *
+     * <p>
+     * <strong>Note:</strong> XML parsing is a resource-heavy operation that may
+     * stall, so this method should not be called while holding a lock.
+     * </p>
+     */
+    private List<AccessibilityShortcutInfo> parseAccessibilityShortcutInfos(int userId) {
+        // TODO: b/297279151 - This should be implemented here, not by AccessibilityManager.
+        return AccessibilityManager.getInstance(mContext)
+                .getInstalledAccessibilityShortcutListAsUser(mContext, userId);
+    }
+
+    private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState,
+            List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
+        if (!parsedAccessibilityShortcutInfos.equals(userState.mInstalledShortcuts)) {
             userState.mInstalledShortcuts.clear();
-            userState.mInstalledShortcuts.addAll(shortcutInfos);
+            userState.mInstalledShortcuts.addAll(parsedAccessibilityShortcutInfos);
             return true;
         }
         return false;
@@ -2890,9 +2986,23 @@
         userState.setFilterKeyEventsEnabledLocked(false);
     }
 
+    // ErrorProne doesn't understand that this method is only called while locked,
+    // returning an error for accessing mCurrentUserId.
+    @SuppressWarnings("GuardedBy")
     private boolean readConfigurationForUserStateLocked(AccessibilityUserState userState) {
-        boolean somethingChanged = readInstalledAccessibilityServiceLocked(userState);
-        somethingChanged |= readInstalledAccessibilityShortcutLocked(userState);
+        return readConfigurationForUserStateLocked(userState,
+                parseAccessibilityServiceInfos(mCurrentUserId),
+                parseAccessibilityShortcutInfos(mCurrentUserId));
+    }
+
+    private boolean readConfigurationForUserStateLocked(
+            AccessibilityUserState userState,
+            List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos,
+            List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
+        boolean somethingChanged = readInstalledAccessibilityServiceLocked(
+                userState, parsedAccessibilityServiceInfos);
+        somethingChanged |= readInstalledAccessibilityShortcutLocked(
+                userState, parsedAccessibilityShortcutInfos);
         somethingChanged |= readEnabledAccessibilityServicesLocked(userState);
         somethingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState);
         somethingChanged |= readTouchExplorationEnabledSettingLocked(userState);
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index c418485..fc8d4f8 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -882,10 +882,22 @@
         final int pointerIndex = event.findPointerIndex(pointerId);
         switch (event.getPointerCount()) {
             case 1:
-            // Touch exploration.
+                // Touch exploration.
                 sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
-                mDispatcher.sendMotionEvent(
-                        event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
+                if (Flags.reduceTouchExplorationSensitivity()
+                        && mState.getLastInjectedHoverEvent() != null) {
+                    final MotionEvent lastEvent = mState.getLastInjectedHoverEvent();
+                    final float deltaX = lastEvent.getX() - rawEvent.getX();
+                    final float deltaY = lastEvent.getY() - rawEvent.getY();
+                    final double moveDelta = Math.hypot(deltaX, deltaY);
+                    if (moveDelta > mTouchSlop) {
+                        mDispatcher.sendMotionEvent(
+                                event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
+                    }
+                } else {
+                    mDispatcher.sendMotionEvent(
+                            event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
+                }
                 break;
             case 2:
                 if (mGestureDetector.isMultiFingerGesturesEnabled()
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 72242d2..e4f1d3a 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -363,6 +363,7 @@
                 case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS:
                 case AutofillFeatureFlags.DEVICE_CONFIG_PREFER_PROVIDER_OVER_PCC:
                 case AutofillFeatureFlags.DEVICE_CONFIG_PCC_USE_FALLBACK:
+                case Flags.FLAG_AUTOFILL_CREDMAN_INTEGRATION:
                     setDeviceConfigProperties();
                     break;
                 case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES:
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 8a2aa61..f5562d2 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -122,9 +122,9 @@
     private static final String TAG = "VirtualDeviceImpl";
 
     /**
-     * Virtual displays created by a {@link VirtualDeviceManager.VirtualDevice} are more consistent
-     * with virtual displays created via {@link DisplayManager} and allow for the creation of
-     * private, auto-mirror, and fixed orientation displays since
+     * Virtual displays created by a {@code VirtualDeviceManager.VirtualDevice} are more consistent
+     * with virtual displays created via {@link android.hardware.display.DisplayManager} and allow
+     * for the creation of private, auto-mirror, and fixed orientation displays since
      * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}.
      *
      * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index f594170..ee41a69 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -20,11 +20,15 @@
 import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE;
 import static android.service.contentcapture.ContentCaptureService.setClientState;
 import static android.view.contentcapture.ContentCaptureHelper.toList;
+import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG;
+import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD;
+import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG;
 import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE;
 import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_OK;
 import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_SECURITY_EXCEPTION;
 import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_TRUE;
 import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED;
+import static android.view.contentprotection.flags.Flags.parseGroupsConfigEnabled;
 
 import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ACCEPT_DATA_SHARE_REQUEST;
 import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CLIENT_PIPE_FAIL;
@@ -112,6 +116,8 @@
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
@@ -138,6 +144,9 @@
     private static final int MAX_CONCURRENT_FILE_SHARING_REQUESTS = 10;
     private static final int DATA_SHARE_BYTE_BUFFER_LENGTH = 1_024;
 
+    private static final String CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_GROUP = ";";
+    private static final String CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_VALUE = ",";
+
     // Needed to pass checkstyle_hook as names are too long for one line.
     private static final int EVENT__DATA_SHARE_ERROR_CONCURRENT_REQUEST =
             CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CONCURRENT_REQUEST;
@@ -203,6 +212,17 @@
     @GuardedBy("mLock")
     int mDevCfgContentProtectionBufferSize;
 
+    @GuardedBy("mLock")
+    @NonNull
+    List<List<String>> mDevCfgContentProtectionRequiredGroups;
+
+    @GuardedBy("mLock")
+    @NonNull
+    List<List<String>> mDevCfgContentProtectionOptionalGroups;
+
+    @GuardedBy("mLock")
+    int mDevCfgContentProtectionOptionalGroupsThreshold;
+
     private final Executor mDataShareExecutor = Executors.newCachedThreadPool();
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
@@ -226,6 +246,11 @@
                 com.android.internal.R.string.config_defaultContentCaptureService),
                 UserManager.DISALLOW_CONTENT_CAPTURE,
                 /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_NO_REFRESH);
+
+        mDevCfgContentProtectionRequiredGroups =
+                ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS;
+        mDevCfgContentProtectionOptionalGroups =
+                ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS;
         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
                 ActivityThread.currentApplication().getMainExecutor(),
                 (properties) -> onDeviceConfigChange(properties));
@@ -422,6 +447,9 @@
                 case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE:
                 case ContentCaptureManager
                         .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE:
+                case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG:
+                case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG:
+                case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD:
                     setFineTuneParamsFromDeviceConfig();
                     return;
                 default:
@@ -433,6 +461,8 @@
     /** @hide */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected void setFineTuneParamsFromDeviceConfig() {
+        String contentProtectionRequiredGroupsConfig;
+        String contentProtectionOptionalGroupsConfig;
         synchronized (mLock) {
             mDevCfgMaxBufferSize =
                     DeviceConfig.getInt(
@@ -486,6 +516,24 @@
                             ContentCaptureManager
                                     .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE,
                             ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE);
+            contentProtectionRequiredGroupsConfig =
+                    DeviceConfig.getString(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG,
+                            ContentCaptureManager
+                                    .DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG);
+            contentProtectionOptionalGroupsConfig =
+                    DeviceConfig.getString(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG,
+                            ContentCaptureManager
+                                    .DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG);
+            mDevCfgContentProtectionOptionalGroupsThreshold =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD,
+                            ContentCaptureManager
+                                    .DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD);
             if (verbose) {
                 Slog.v(
                         TAG,
@@ -507,9 +555,24 @@
                                 + ", contentProtectionAppsBlocklistSize="
                                 + mDevCfgContentProtectionAppsBlocklistSize
                                 + ", contentProtectionBufferSize="
-                                + mDevCfgContentProtectionBufferSize);
+                                + mDevCfgContentProtectionBufferSize
+                                + ", contentProtectionRequiredGroupsConfig="
+                                + contentProtectionRequiredGroupsConfig
+                                + ", contentProtectionOptionalGroupsConfig="
+                                + contentProtectionOptionalGroupsConfig
+                                + ", contentProtectionOptionalGroupsThreshold="
+                                + mDevCfgContentProtectionOptionalGroupsThreshold);
             }
         }
+
+        List<List<String>> contentProtectionRequiredGroups =
+                parseContentProtectionGroupsConfig(contentProtectionRequiredGroupsConfig);
+        List<List<String>> contentProtectionOptionalGroups =
+                parseContentProtectionGroupsConfig(contentProtectionOptionalGroupsConfig);
+        synchronized (mLock) {
+            mDevCfgContentProtectionRequiredGroups = contentProtectionRequiredGroups;
+            mDevCfgContentProtectionOptionalGroups = contentProtectionOptionalGroups;
+        }
     }
 
     private void setLoggingLevelFromDeviceConfig() {
@@ -786,6 +849,15 @@
         pw.print(prefix2);
         pw.print("contentProtectionBufferSize: ");
         pw.println(mDevCfgContentProtectionBufferSize);
+        pw.print(prefix2);
+        pw.print("contentProtectionRequiredGroupsSize: ");
+        pw.println(mDevCfgContentProtectionRequiredGroups.size());
+        pw.print(prefix2);
+        pw.print("contentProtectionOptionalGroupsSize: ");
+        pw.println(mDevCfgContentProtectionOptionalGroups.size());
+        pw.print(prefix2);
+        pw.print("contentProtectionOptionalGroupsThreshold: ");
+        pw.println(mDevCfgContentProtectionOptionalGroupsThreshold);
         pw.print(prefix);
         pw.println("Global Options:");
         mGlobalContentCaptureOptions.dump(prefix2, pw);
@@ -890,6 +962,42 @@
         return mContentCaptureManagerServiceStub;
     }
 
+    /**
+     * Parses a simple config in format "group;group" where each "group" is itself in the format of
+     * "string1,string2", eg:
+     *
+     * <p>"a" -> [["a"]]
+     *
+     * <p>"a,b" -> [["a", "b"]]
+     *
+     * <p>"a,b;c;d,e" -> [["a", "b"], ["c"], ["d", "e"]]
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull
+    protected List<List<String>> parseContentProtectionGroupsConfig(@Nullable String config) {
+        if (verbose) {
+            Slog.v(TAG, "parseContentProtectionGroupsConfig: " + config);
+        }
+        if (!parseGroupsConfigEnabled()) {
+            return Collections.emptyList();
+        }
+        if (config == null) {
+            return Collections.emptyList();
+        }
+        return Arrays.stream(config.split(CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_GROUP))
+                .map(this::parseContentProtectionGroupConfigValues)
+                .filter(group -> !group.isEmpty())
+                .toList();
+    }
+
+    private List<String> parseContentProtectionGroupConfigValues(@NonNull String group) {
+        return Arrays.stream(group.split(CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_VALUE))
+                .filter(value -> !value.isEmpty())
+                .toList();
+    }
+
     final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub {
 
         @Override
@@ -1277,7 +1385,10 @@
                                 isContentCaptureReceiverEnabled || whitelistedComponents != null,
                                 new ContentCaptureOptions.ContentProtectionOptions(
                                         isContentProtectionReceiverEnabled,
-                                        mDevCfgContentProtectionBufferSize),
+                                        mDevCfgContentProtectionBufferSize,
+                                        mDevCfgContentProtectionRequiredGroups,
+                                        mDevCfgContentProtectionOptionalGroups,
+                                        mDevCfgContentProtectionOptionalGroupsThreshold),
                                 whitelistedComponents);
                 if (verbose) Slog.v(TAG, "getOptionsForPackage(" + packageName + "): " + options);
                 return options;
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 962f38f..beea063 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -214,9 +214,12 @@
     // external storage service.
     public static final int FAILED_MOUNT_RESET_TIMEOUT_SECONDS = 10;
 
-     /** Extended timeout for the system server watchdog. */
+    /** Extended timeout for the system server watchdog. */
     private static final int SLOW_OPERATION_WATCHDOG_TIMEOUT_MS = 60 * 1000;
 
+    /** Extended timeout for the system server watchdog for vold#partition operation. */
+    private static final int PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS = 3 * 60 * 1000;
+
     @GuardedBy("mLock")
     private final Set<Integer> mFuseMountedUser = new ArraySet<>();
 
@@ -2338,6 +2341,8 @@
         try {
             // TODO(b/135341433): Remove cautious logging when FUSE is stable
             Slog.i(TAG, "Mounting volume " + vol);
+            Watchdog.getInstance().setOneOffTimeoutForMonitors(
+                    SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#mount might be slow");
             mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
                 @Override
                 public boolean onVolumeChecking(FileDescriptor fd, String path,
@@ -2463,10 +2468,12 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS)
     @Override
     public void partitionPublic(String diskId) {
-
         super.partitionPublic_enforcePermission();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+
+        Watchdog.getInstance().setOneOffTimeoutForMonitors(
+                PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
             waitForLatch(latch, "partitionPublic", 3 * DateUtils.MINUTE_IN_MILLIS);
@@ -2483,6 +2490,9 @@
         enforceAdminUser();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+
+        Watchdog.getInstance().setOneOffTimeoutForMonitors(
+                PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1);
             waitForLatch(latch, "partitionPrivate", 3 * DateUtils.MINUTE_IN_MILLIS);
@@ -2499,6 +2509,9 @@
         enforceAdminUser();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+
+        Watchdog.getInstance().setOneOffTimeoutForMonitors(
+                PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio);
             waitForLatch(latch, "partitionMixed", 3 * DateUtils.MINUTE_IN_MILLIS);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 553b085..0956c6d 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -409,6 +409,13 @@
 
     AppWidgetManagerInternal mAppWidgetManagerInternal;
 
+    /**
+     * The available ANR timers.
+     */
+    private final ProcessAnrTimer mActiveServiceAnrTimer;
+    private final ServiceAnrTimer mShortFGSAnrTimer;
+    private final ServiceAnrTimer mServiceFGAnrTimer;
+
     // allowlisted packageName.
     ArraySet<String> mAllowListWhileInUsePermissionInFgs = new ArraySet<>();
 
@@ -663,6 +670,15 @@
 
         final IBinder b = ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
         this.mFGSLogger = new ForegroundServiceTypeLoggerModule();
+        this.mActiveServiceAnrTimer = new ProcessAnrTimer(service,
+                ActivityManagerService.SERVICE_TIMEOUT_MSG,
+                "SERVICE_TIMEOUT");
+        this.mShortFGSAnrTimer = new ServiceAnrTimer(service,
+                ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG,
+                "FGS_TIMEOUT");
+        this.mServiceFGAnrTimer = new ServiceAnrTimer(service,
+                ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG,
+                "SERVICE_FOREGROUND_TIMEOUT");
     }
 
     void systemServicesReady() {
@@ -2083,8 +2099,7 @@
                 r.fgRequired = false;
                 r.fgWaiting = false;
                 alreadyStartedOp = stopProcStatsOp = true;
-                mAm.mHandler.removeMessages(
-                        ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
+                mServiceFGAnrTimer.cancel(r);
             }
 
             final ProcessServiceRecord psr = r.app.mServices;
@@ -3313,7 +3328,7 @@
     }
 
     void unscheduleShortFgsTimeoutLocked(ServiceRecord sr) {
-        mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
+        mShortFGSAnrTimer.cancel(sr);
         mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_PROCSTATE_TIMEOUT_MSG,
                 sr);
         mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr);
@@ -3387,9 +3402,11 @@
                     Slog.d(TAG_SERVICE, "[STALE] Short FGS timed out: " + sr
                             + " " + sr.getShortFgsTimedEventDescription(nowUptime));
                 }
+                mShortFGSAnrTimer.discard(sr);
                 return;
             }
             Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr);
+            mShortFGSAnrTimer.accept(sr);
             traceInstant("short FGS timeout: ", sr);
 
             logFGSStateChangeLocked(sr,
@@ -3413,11 +3430,10 @@
                         msg, sr.getShortFgsInfo().getProcStateDemoteTime());
             }
 
-            {
-                final Message msg = mAm.mHandler.obtainMessage(
-                        ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
-                mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getAnrTime());
-            }
+            // ServiceRecord.getAnrTime() is an absolute time with a reference that is not "now".
+            // Compute the time from "now" when starting the anr timer.
+            mShortFGSAnrTimer.start(sr,
+                    sr.getShortFgsInfo().getAnrTime() - SystemClock.uptimeMillis());
         }
     }
 
@@ -4847,8 +4863,7 @@
         // a new SERVICE_FOREGROUND_TIMEOUT_MSG is scheduled in SERVICE_START_FOREGROUND_TIMEOUT
         // again.
         if (r.fgRequired && r.fgWaiting) {
-            mAm.mHandler.removeMessages(
-                    ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
+            mServiceFGAnrTimer.cancel(r);
             r.fgWaiting = false;
         }
 
@@ -5691,8 +5706,7 @@
             }
             mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
                     AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
-            mAm.mHandler.removeMessages(
-                    ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
+            mServiceFGAnrTimer.cancel(r);
             if (r.app != null) {
                 Message msg = mAm.mHandler.obtainMessage(
                         ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
@@ -6128,7 +6142,7 @@
                 if (psr.numberOfExecutingServices() == 0) {
                     if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING,
                             "No more executingServices of " + r.shortInstanceName);
-                    mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
+                    if (r.app.mPid != 0) mActiveServiceAnrTimer.cancel(r.app);
                 } else if (r.executeFg) {
                     // Need to re-evaluate whether the app still needs to be in the foreground.
                     for (int i = psr.numberOfExecutingServices() - 1; i >= 0; i--) {
@@ -6816,13 +6830,16 @@
             synchronized (mAm) {
                 if (proc.isDebugging()) {
                     // The app's being debugged, ignore timeout.
+                    mActiveServiceAnrTimer.discard(proc);
                     return;
                 }
                 final ProcessServiceRecord psr = proc.mServices;
                 if (psr.numberOfExecutingServices() == 0 || proc.getThread() == null
                         || proc.isKilled()) {
+                    mActiveServiceAnrTimer.discard(proc);
                     return;
                 }
+                mActiveServiceAnrTimer.accept(proc);
                 final long now = SystemClock.uptimeMillis();
                 final long maxTime =  now
                         - (psr.shouldExecServicesFg()
@@ -6855,12 +6872,11 @@
                     timeoutRecord = TimeoutRecord.forServiceExec(timeout.shortInstanceName,
                             waitedMillis);
                 } else {
-                    Message msg = mAm.mHandler.obtainMessage(
-                            ActivityManagerService.SERVICE_TIMEOUT_MSG);
-                    msg.obj = proc;
-                    mAm.mHandler.sendMessageAtTime(msg, psr.shouldExecServicesFg()
-                            ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) :
-                            (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT));
+                    final long delay = psr.shouldExecServicesFg()
+                                       ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) :
+                                       (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT)
+                                       - SystemClock.uptimeMillis();
+                    mActiveServiceAnrTimer.start(proc, delay);
                 }
             }
 
@@ -6886,12 +6902,15 @@
             synchronized (mAm) {
                 timeoutRecord.mLatencyTracker.waitingOnAMSLockEnded();
                 if (!r.fgRequired || !r.fgWaiting || r.destroying) {
+                    mServiceFGAnrTimer.discard(r);
                     return;
                 }
 
+                mServiceFGAnrTimer.accept(r);
                 app = r.app;
                 if (app != null && app.isDebugging()) {
                     // The app's being debugged; let it ride
+                    mServiceFGAnrTimer.discard(r);
                     return;
                 }
 
@@ -6948,26 +6967,46 @@
                 ForegroundServiceDidNotStartInTimeException.createExtrasForService(service));
     }
 
+    private static class ProcessAnrTimer extends AnrTimer<ProcessRecord> {
+
+        ProcessAnrTimer(ActivityManagerService am, int msg, String label) {
+            super(Objects.requireNonNull(am).mHandler, msg, label);
+        }
+
+        void start(@NonNull ProcessRecord proc, long millis) {
+            start(proc, proc.getPid(), proc.uid, millis);
+        }
+    }
+
+    private static class ServiceAnrTimer extends AnrTimer<ServiceRecord> {
+
+        ServiceAnrTimer(ActivityManagerService am, int msg, String label) {
+            super(Objects.requireNonNull(am).mHandler, msg, label);
+        }
+
+        void start(@NonNull ServiceRecord service, long millis) {
+            start(service,
+                    (service.app != null) ? service.app.getPid() : 0,
+                    service.appInfo.uid,
+                    millis);
+        }
+    }
+
     void scheduleServiceTimeoutLocked(ProcessRecord proc) {
         if (proc.mServices.numberOfExecutingServices() == 0 || proc.getThread() == null) {
             return;
         }
-        Message msg = mAm.mHandler.obtainMessage(
-                ActivityManagerService.SERVICE_TIMEOUT_MSG);
-        msg.obj = proc;
-        mAm.mHandler.sendMessageDelayed(msg, proc.mServices.shouldExecServicesFg()
-                ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT);
+        final long delay = proc.mServices.shouldExecServicesFg()
+                ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT;
+        mActiveServiceAnrTimer.start(proc, delay);
     }
 
     void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
         if (r.app.mServices.numberOfExecutingServices() == 0 || r.app.getThread() == null) {
             return;
         }
-        Message msg = mAm.mHandler.obtainMessage(
-                ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG);
-        msg.obj = r;
         r.fgWaiting = true;
-        mAm.mHandler.sendMessageDelayed(msg, mAm.mConstants.mServiceStartForegroundTimeoutMs);
+        mServiceFGAnrTimer.start(r, mAm.mConstants.mServiceStartForegroundTimeoutMs);
     }
 
     final class ServiceDumper {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a97f005..b43b986 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1532,6 +1532,11 @@
      */
     int mBootPhase;
 
+    /**
+     * The time stamp that all apps have received BOOT_COMPLETED.
+     */
+    volatile long mBootCompletedTimestamp;
+
     @GuardedBy("this")
     boolean mDeterministicUidIdle = false;
 
@@ -5164,10 +5169,14 @@
                         public void performReceive(Intent intent, int resultCode,
                                 String data, Bundle extras, boolean ordered,
                                 boolean sticky, int sendingUser) {
-                            synchronized (mProcLock) {
-                                mAppProfiler.requestPssAllProcsLPr(
-                                        SystemClock.uptimeMillis(), true, false);
-                            }
+                            mBootCompletedTimestamp = SystemClock.uptimeMillis();
+                            // Defer the full Pss collection as the system is really busy now.
+                            mHandler.postDelayed(() -> {
+                                synchronized (mProcLock) {
+                                    mAppProfiler.requestPssAllProcsLPr(
+                                            SystemClock.uptimeMillis(), true, false);
+                                }
+                            }, mConstants.FULL_PSS_MIN_INTERVAL);
                         }
                     });
             maybeLogUserspaceRebootEvent();
diff --git a/services/core/java/com/android/server/am/AnrTimer.java b/services/core/java/com/android/server/am/AnrTimer.java
index 9ba49ce..3e17930 100644
--- a/services/core/java/com/android/server/am/AnrTimer.java
+++ b/services/core/java/com/android/server/am/AnrTimer.java
@@ -28,6 +28,7 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.text.TextUtils;
+import android.text.format.TimeMigrationUtils;
 import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
@@ -44,7 +45,6 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Date;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -150,7 +150,7 @@
         /** A partial stack that localizes the caller of the operation. */
         final StackTraceElement[] stack;
         /** The date, in local time, the error was created. */
-        final String date;
+        final long timestamp;
 
         Error(@NonNull String issue, @NonNull String operation, @NonNull String tag,
                 @NonNull StackTraceElement[] stack, @NonNull String arg) {
@@ -159,7 +159,7 @@
             this.tag = tag;
             this.stack = stack;
             this.arg = arg;
-            this.date = new Date().toString();
+            this.timestamp = SystemClock.elapsedRealtime();
         }
     }
 
@@ -347,20 +347,23 @@
          * main Looper.
          */
         @NonNull
-        Handler getHandler(@NonNull Handler.Callback callback) {
+        Handler newHandler(@NonNull Handler.Callback callback) {
             Looper looper = mReferenceHandler.getLooper();
             if (looper == null) looper = Looper.getMainLooper();
             return new Handler(looper, callback);
-        };
+        }
 
-        /** Return a CpuTracker. */
+        /**
+         * Return a CpuTracker. The default behavior is to create a new CpuTracker but this changes
+         * for unit tests.
+         **/
         @NonNull
-        CpuTracker getTracker() {
+        CpuTracker newTracker() {
             return new CpuTracker();
         }
 
         /** Return true if the feature is enabled. */
-        boolean getFeatureEnabled() {
+        boolean isFeatureEnabled() {
             return anrTimerServiceEnabled();
         }
     }
@@ -401,8 +404,8 @@
         /** Create a HandlerTimerService that directly uses the supplied handler and tracker. */
         @VisibleForTesting
         HandlerTimerService(@NonNull Injector injector) {
-            mHandler = injector.getHandler(this::expires);
-            mCpu = injector.getTracker();
+            mHandler = injector.newHandler(this::expires);
+            mCpu = injector.newTracker();
         }
 
         /** Post a message with the specified timeout.  The timer is not modified. */
@@ -513,7 +516,26 @@
     private final FeatureSwitch mFeature;
 
     /**
-     * The common constructor.  A null injector results in a normal, production timer.
+     * Create one AnrTimer instance.  The instance is given a handler and a "what".  Individual
+     * timers are started with {@link #start}.  If a timer expires, then a {@link Message} is sent
+     * immediately to the handler with {@link Message.what} set to what and {@link Message.obj} set
+     * to the timer key.
+     *
+     * AnrTimer instances have a label, which must be unique.  The label is used for reporting and
+     * debug.
+     *
+     * If an individual timer expires internally, and the "extend" parameter is true, then the
+     * AnrTimer may extend the individual timer rather than immediately delivering the timeout to
+     * the client.  The extension policy is not part of the instance.
+     *
+     * This method accepts an {@link #Injector} to tune behavior for testing.  This method should
+     * not be called directly by regular clients.
+     *
+     * @param handler The handler to which the expiration message will be delivered.
+     * @param what The "what" parameter for the expiration message.
+     * @param label A name for this instance.
+     * @param extend A flag to indicate if expired timers can be granted extensions.
+     * @param injector An {@link #Injector} to tune behavior for testing.
      */
     @VisibleForTesting
     AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend,
@@ -522,7 +544,7 @@
         mWhat = what;
         mLabel = label;
         mExtend = extend;
-        boolean enabled = injector.getFeatureEnabled();
+        boolean enabled = injector.isFeatureEnabled();
         if (!enabled) {
             mFeature = new FeatureDisabled();
             mTimerService = null;
@@ -538,14 +560,25 @@
     }
 
     /**
-     * Create one timer instance for production.  The client can ask for extensible timeouts.
+     * Create an AnrTimer instance with the default {@link #Injector}.  See {@link AnrTimer(Handler,
+     * int, String, boolean, Injector} for a functional description.
+     *
+     * @param handler The handler to which the expiration message will be delivered.
+     * @param what The "what" parameter for the expiration message.
+     * @param label A name for this instance.
+     * @param extend A flag to indicate if expired timers can be granted extensions.
      */
     AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) {
         this(handler, what, label, extend, new Injector(handler));
     }
 
     /**
-     * Create one timer instance for production.  There are no extensible timeouts.
+     * Create an AnrTimer instance with the default {@link #Injector} and with extensions disabled.
+     * See {@link AnrTimer(Handler, int, String, boolean, Injector} for a functional description.
+     *
+     * @param handler The handler to which the expiration message will be delivered.
+     * @param what The "what" parameter for the expiration message.
+     * @param label A name for this instance.
      */
     AnrTimer(@NonNull Handler handler, int what, @NonNull String label) {
         this(handler, what, label, false);
@@ -555,6 +588,8 @@
      * Return true if the service is enabled on this instance.  Clients should use this method to
      * decide if the feature is enabled, and not read the flags directly.  This method should be
      * deleted if and when the feature is enabled permanently.
+     *
+     * @return true if the service is flag-enabled.
      */
     boolean serviceEnabled() {
         return mFeature.enabled();
@@ -642,7 +677,7 @@
     }
 
     /**
-     * Report something about a timer.
+     * Generate a log message for a timer.
      */
     private void report(@NonNull Timer timer, @NonNull String msg) {
         Log.i(TAG, msg + " " + timer + " " + Objects.toString(timer.arg));
@@ -654,9 +689,13 @@
      */
     private abstract class FeatureSwitch {
         abstract boolean start(@NonNull V arg, int pid, int uid, long timeoutMs);
+
         abstract boolean cancel(@NonNull V arg);
+
         abstract boolean accept(@NonNull V arg);
+
         abstract boolean discard(@NonNull V arg);
+
         abstract boolean enabled();
     }
 
@@ -666,6 +705,7 @@
      */
     private class FeatureDisabled extends FeatureSwitch {
         /** Start a timer by sending a message to the client's handler. */
+        @Override
         boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
             final Message msg = mHandler.obtainMessage(mWhat, arg);
             mHandler.sendMessageDelayed(msg, timeoutMs);
@@ -673,22 +713,26 @@
         }
 
         /** Cancel a timer by removing the message from the client's handler. */
+        @Override
         boolean cancel(@NonNull V arg) {
             mHandler.removeMessages(mWhat, arg);
             return true;
         }
 
         /** accept() is a no-op when the feature is disabled. */
+        @Override
         boolean accept(@NonNull V arg) {
             return true;
         }
 
         /** discard() is a no-op when the feature is disabled. */
+        @Override
         boolean discard(@NonNull V arg) {
             return true;
         }
 
         /** The feature is not enabled. */
+        @Override
         boolean enabled() {
             return false;
         }
@@ -703,16 +747,17 @@
         /**
          * Start a timer.
          */
+        @Override
         boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
             final Timer timer = Timer.obtain(pid, uid, arg, timeoutMs, AnrTimer.this);
             synchronized (mLock) {
                 Timer old = mTimerMap.get(arg);
+                // There is an existing timer.  If the timer was running, then cancel the running
+                // timer and restart it.  If the timer was expired record a protocol error and
+                // discard the expired timer.
                 if (old != null) {
-                    // There is an existing timer.  This is a protocol error in the client.
-                    // Record the error and then clean up by canceling running timers and
-                    // discarding expired timers.
-                    restartedLocked(old.status, arg);
                     if (old.status == TIMER_EXPIRED) {
+                      restartedLocked(old.status, arg);
                         discard(arg);
                     } else {
                         cancel(arg);
@@ -735,6 +780,7 @@
         /**
          * Cancel a timer.  Return false if the timer was not found.
          */
+        @Override
         boolean cancel(@NonNull V arg) {
             synchronized (mLock) {
                 Timer timer = removeLocked(arg);
@@ -755,6 +801,7 @@
          * Accept a timer in the framework-level handler.  The timeout has been accepted and the
          * timeout handler is executing.  Return false if the timer was not found.
          */
+        @Override
         boolean accept(@NonNull V arg) {
             synchronized (mLock) {
                 Timer timer = removeLocked(arg);
@@ -775,6 +822,7 @@
          * longer interesting.  No statistics are collected.  Return false if the time was not
          * found.
          */
+        @Override
         boolean discard(@NonNull V arg) {
             synchronized (mLock) {
                 Timer timer = removeLocked(arg);
@@ -791,40 +839,58 @@
         }
 
         /** The feature is enabled. */
+        @Override
         boolean enabled() {
             return true;
         }
     }
 
     /**
-     * Start a timer associated with arg.  If a timer already exists with the same arg, then that
-     * timer is canceled and a new timer is created.  This returns false if the timer cannot be
-     * created.
+     * Start a timer associated with arg.  The same object must be used to cancel, accept, or
+     * discard a timer later.  If a timer already exists with the same arg, then the existing timer
+     * is canceled and a new timer is created.
+     *
+     * @param arg The key by which the timer is known.  This is never examined or modified.
+     * @param pid The Linux process ID of the target being timed.
+     * @param uid The Linux user ID of the target being timed.
+     * @param timeoutMs The timer timeout, in milliseconds.
+     * @return true if the timer was successfully created.
      */
     boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
         return mFeature.start(arg, pid, uid, timeoutMs);
     }
 
     /**
-     * Cancel a running timer and remove it from any list.  This returns true if the timer was
-     * found and false otherwise.  It is not an error to cancel a non-existent timer.  It is also
-     * not an error to cancel an expired timer.
+     * Cancel the running timer associated with arg.  The timer is forgotten.  If the timer has
+     * expired, the call is treated as a discard.  No errors are reported if the timer does not
+     * exist or if the timer has expired.
+     *
+     * @return true if the timer was found and was running.
      */
     boolean cancel(@NonNull V arg) {
         return mFeature.cancel(arg);
     }
 
     /**
-     * Accept an expired timer.  This returns false if the timer was not found or if the timer was
-     * not expired.
+     * Accept the expired timer associated with arg.  This indicates that the caller considers the
+     * timer expiration to be a true ANR.  (See {@link #discard} for an alternate response.)  It is
+     * an error to accept a running timer, however the running timer will be canceled.
+     *
+     * @return true if the timer was found and was expired.
      */
     boolean accept(@NonNull V arg) {
         return mFeature.accept(arg);
     }
 
     /**
-     * Discard an expired timer.  This returns false if the timer was not found or if the timer was
-     * not expired.
+     * Discard the expired timer associated with arg.  This indicates that the caller considers the
+     * timer expiration to be a false ANR.  ((See {@link #accept} for an alternate response.)  One
+     * reason to discard an expired timer is if the process being timed was also being debugged:
+     * such a process could be stopped at a breakpoint and its failure to respond would not be an
+     * error.  It is an error to discard a running timer, however the running timer will be
+     * canceled.
+     *
+     * @return true if the timer was found and was expired.
      */
     boolean discard(@NonNull V arg) {
         return mFeature.discard(arg);
@@ -913,7 +979,10 @@
     private static void dump(IndentingPrintWriter ipw, int seq, Error err) {
         ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, err.operation, err.tag,
                 err.issue, err.arg);
-        ipw.format("    date:%s\n", err.date);
+
+        final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime();
+        final long etime = offset + err.timestamp;
+        ipw.println("    date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime));
         ipw.increaseIndent();
         for (int i = 0; i < err.stack.length; i++) {
             ipw.println("    " + err.stack[i].toString());
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index a428907..d19eae5 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -258,7 +258,8 @@
     private static final int MSG_PROCESS_FREEZABLE_CHANGED = 6;
     private static final int MSG_UID_STATE_CHANGED = 7;
 
-    // Required when Flags.anrTimerServiceEnabled is false.
+    // Required when Flags.anrTimerServiceEnabled is false.  This constant should be deleted if and
+    // when the flag is fused on.
     private static final int MSG_DELIVERY_TIMEOUT_SOFT = 8;
 
     private void enqueueUpdateRunningList() {
@@ -274,7 +275,8 @@
                 updateRunningList();
                 return true;
             }
-            // Required when Flags.anrTimerServiceEnabled is false.
+            // Required when Flags.anrTimerServiceEnabled is false.  This case should be deleted if
+            // and when the flag is fused on.
             case MSG_DELIVERY_TIMEOUT_SOFT: {
                 synchronized (mService) {
                     deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj, msg.arg1);
@@ -1169,7 +1171,8 @@
         r.resultTo = null;
     }
 
-    // Required when Flags.anrTimerServiceEnabled is false.
+    // Required when Flags.anrTimerServiceEnabled is false.  This function can be replaced with a
+    // single call to {@code mAnrTimer.start()} if and when the flag is fused on.
     private void startDeliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue,
             int softTimeoutMillis) {
         if (mAnrTimer.serviceEnabled()) {
@@ -1181,7 +1184,8 @@
         }
     }
 
-    // Required when Flags.anrTimerServiceEnabled is false.
+    // Required when Flags.anrTimerServiceEnabled is false. This function can be replaced with a
+    // single call to {@code mAnrTimer.cancel()} if and when the flag is fused on.
     private void cancelDeliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue) {
         mAnrTimer.cancel(queue);
         if (!mAnrTimer.serviceEnabled()) {
@@ -1189,7 +1193,8 @@
         }
     }
 
-    // Required when Flags.anrTimerServiceEnabled is false.
+    // Required when Flags.anrTimerServiceEnabled is false.  This function can be deleted entirely
+    // if and when the flag is fused on.
     private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue,
             int softTimeoutMillis) {
         if (queue.app != null) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 4572766..e0e6cad 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1439,7 +1439,7 @@
     }
 
     public static long computeNextPssTime(int procState, ProcStateMemTracker tracker, boolean test,
-            boolean sleeping, long now) {
+            boolean sleeping, long now, long earliest) {
         boolean first;
         float scalingFactor;
         final int memState = sProcStateToProcMem[procState];
@@ -1470,7 +1470,7 @@
         if (delay > PSS_MAX_INTERVAL) {
             delay = PSS_MAX_INTERVAL;
         }
-        return now + delay;
+        return Math.max(now + delay, earliest);
     }
 
     long getMemLevel(int adjustment) {
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index c1f86e0..940c58b 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -575,7 +575,11 @@
 
     @GuardedBy("mProfilerLock")
     long computeNextPssTime(int procState, boolean test, boolean sleeping, long now) {
-        return ProcessList.computeNextPssTime(procState, mProcStateMemTracker, test, sleeping, now);
+        return ProcessList.computeNextPssTime(procState, mProcStateMemTracker, test, sleeping, now,
+                // Cap the Pss time to make sure no Pss is collected during the very few
+                // minutes after the system is boot, given the system is already busy.
+                Math.max(mService.mBootCompletedTimestamp, mService.mLastIdleTime)
+                + mService.mConstants.FULL_PSS_MIN_INTERVAL);
     }
 
     private static void commitNextPssTime(ProcStateMemTracker tracker) {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 1ba1f55..16e3fdf2 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -163,9 +163,12 @@
         "wear_system_health",
         "wear_systems",
         "window_surfaces",
-        "windowing_frontend"
+        "windowing_frontend",
     };
 
+    public static final String NAMESPACE_REBOOT_STAGING = "staged";
+    public static final String NAMESPACE_REBOOT_STAGING_DELIMITER = "*";
+
     private final String[] mGlobalSettings;
 
     private final String[] mDeviceConfigScopes;
@@ -261,6 +264,22 @@
                         }
                     });
         }
+
+        // add sys prop sync callback for staged flag values
+        DeviceConfig.addOnPropertiesChangedListener(
+            NAMESPACE_REBOOT_STAGING,
+            AsyncTask.THREAD_POOL_EXECUTOR,
+            (DeviceConfig.Properties properties) -> {
+              String scope = properties.getNamespace();
+              for (String key : properties.getKeyset()) {
+                String aconfigPropertyName = makeAconfigFlagStagedPropertyName(key);
+                if (aconfigPropertyName == null) {
+                    log("unable to construct system property for " + scope + "/" + key);
+                    return;
+                }
+                setProperty(aconfigPropertyName, properties.getString(key, null));
+              }
+            });
     }
 
     public static SettingsToPropertiesMapper start(ContentResolver contentResolver) {
@@ -332,6 +351,35 @@
     }
 
     /**
+     * system property name constructing rule for staged aconfig flags, the flag name
+     * is in the form of [namespace]*[actual flag name], we should push the following
+     * to system properties
+     * "next_boot.[actual sys prop name]".
+     * If the name contains invalid characters or substrings for system property name,
+     * will return null.
+     * @param flagName
+     * @return
+     */
+    @VisibleForTesting
+    static String makeAconfigFlagStagedPropertyName(String flagName) {
+        int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
+        if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
+            log("invalid staged flag: " + flagName);
+            return null;
+        }
+
+        String propertyName = "next_boot." + makeAconfigFlagPropertyName(
+                flagName.substring(0, idx), flagName.substring(idx+1));
+
+        if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
+                || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
+            return null;
+        }
+
+        return propertyName;
+    }
+
+    /**
      * system property name constructing rule for aconfig flags:
      * "persist.device_config.aconfig_flags.[category_name].[flag_name]".
      * If the name contains invalid characters or substrings for system property name,
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 6c5f3e7..d65c7c2c 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -189,6 +189,8 @@
 
     private final AtomicBoolean mEnableCsd = new AtomicBoolean(false);
 
+    private final AtomicBoolean mForceCsdProperty = new AtomicBoolean(false);
+
     private final Object mCsdAsAFeatureLock = new Object();
 
     @GuardedBy("mCsdAsAFeatureLock")
@@ -375,9 +377,21 @@
         }
     }
 
+    private boolean updateCsdForTestApi() {
+        if (mForceCsdProperty.get() != SystemProperties.getBoolean(
+                SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, false)) {
+            updateCsdEnabled("SystemPropertiesChangeCallback");
+        }
+
+        return mEnableCsd.get();
+    }
+
     float getCsd() {
         if (!mEnableCsd.get()) {
-            return -1.f;
+            // since this will only be called by a test api enable csd if system property is set
+            if (!updateCsdForTestApi()) {
+                return -1.f;
+            }
         }
 
         final ISoundDose soundDose = mSoundDose.get();
@@ -396,7 +410,10 @@
 
     void setCsd(float csd) {
         if (!mEnableCsd.get()) {
-            return;
+            // since this will only be called by a test api enable csd if system property is set
+            if (!updateCsdForTestApi()) {
+                return;
+            }
         }
 
         SoundDoseRecord[] doseRecordsArray;
@@ -430,7 +447,10 @@
 
     void resetCsdTimeouts() {
         if (!mEnableCsd.get()) {
-            return;
+            // since this will only be called by a test api enable csd if system property is set
+            if (!updateCsdForTestApi()) {
+                return;
+            }
         }
 
         synchronized (mCsdStateLock) {
@@ -440,7 +460,10 @@
 
     void forceUseFrameworkMel(boolean useFrameworkMel) {
         if (!mEnableCsd.get()) {
-            return;
+            // since this will only be called by a test api enable csd if system property is set
+            if (!updateCsdForTestApi()) {
+                return;
+            }
         }
 
         final ISoundDose soundDose = mSoundDose.get();
@@ -458,7 +481,10 @@
 
     void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) {
         if (!mEnableCsd.get()) {
-            return;
+            // since this will only be called by a test api enable csd if system property is set
+            if (!updateCsdForTestApi()) {
+                return;
+            }
         }
 
         final ISoundDose soundDose = mSoundDose.get();
@@ -488,7 +514,7 @@
         try {
             return soundDose.isSoundDoseHalSupported();
         } catch (RemoteException e) {
-            Log.e(TAG, "Exception while forcing CSD computation on all devices", e);
+            Log.e(TAG, "Exception while querying the csd enabled status", e);
         }
         return false;
     }
@@ -544,7 +570,7 @@
             audioDeviceCategory.csdCompatible = isHeadphone;
             soundDose.setAudioDeviceCategory(audioDeviceCategory);
         } catch (RemoteException e) {
-            Log.e(TAG, "Exception while forcing the internal MEL computation", e);
+            Log.e(TAG, "Exception while setting the audio device category", e);
         }
     }
 
@@ -894,7 +920,7 @@
                 mCachedAudioDeviceCategories.clear();
             }
         } catch (RemoteException e) {
-            Log.e(TAG, "Exception while forcing the internal MEL computation", e);
+            Log.e(TAG, "Exception while initializing the cached audio device categories", e);
         }
 
         synchronized (mCsdAsAFeatureLock) {
@@ -991,19 +1017,20 @@
     }
 
     private void updateCsdEnabled(String caller) {
-        boolean csdForce = SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, false);
+        mForceCsdProperty.set(SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE,
+                false));
         // we are using the MCC overlaid legacy flag used for the safe volume enablement
         // to determine whether the MCC enforces any safe hearing standard.
         boolean mccEnforcedSafeMedia = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_safe_media_volume_enabled);
         boolean csdEnable = mContext.getResources().getBoolean(
                 R.bool.config_safe_sound_dosage_enabled);
-        boolean newEnabledCsd = (mccEnforcedSafeMedia && csdEnable) || csdForce;
+        boolean newEnabledCsd = (mccEnforcedSafeMedia && csdEnable) || mForceCsdProperty.get();
 
         synchronized (mCsdAsAFeatureLock) {
             if (!mccEnforcedSafeMedia && csdEnable) {
                 mIsCsdAsAFeatureAvailable = true;
-                newEnabledCsd = mIsCsdAsAFeatureEnabled || csdForce;
+                newEnabledCsd = mIsCsdAsAFeatureEnabled || mForceCsdProperty.get();
                 Log.v(TAG, caller + ": CSD as a feature is not enforced and enabled: "
                         + newEnabledCsd);
             } else {
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 4538cad..1760bb3 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -41,6 +41,7 @@
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IAuthService;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.IBiometricPromptStatusListener;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.IInvalidationCallback;
@@ -357,6 +358,18 @@
         }
 
         @Override
+        public void registerBiometricPromptStatusListener(
+                IBiometricPromptStatusListener listener) throws RemoteException {
+            checkInternalPermission();
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mBiometricService.registerBiometricPromptStatusListener(listener);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void invalidateAuthenticatorIds(int userId, int fromSensorId,
                 IInvalidationCallback callback) throws RemoteException {
             checkInternalPermission();
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 1898b80..9569f23 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -41,6 +41,7 @@
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.IBiometricPromptStatusListener;
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceReceiver;
@@ -88,6 +89,7 @@
 import java.util.Map;
 import java.util.Random;
 import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Supplier;
 
@@ -105,6 +107,8 @@
     @VisibleForTesting
     final SettingObserver mSettingObserver;
     private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
+    private final ConcurrentLinkedQueue<BiometricPromptStatusListener>
+            mBiometricPromptStatusListeners;
     private final Random mRandom = new Random();
     @NonNull private final Supplier<Long> mRequestCounter;
     @NonNull private final BiometricContext mBiometricContext;
@@ -425,6 +429,42 @@
         }
     }
 
+    final class BiometricPromptStatusListener implements IBinder.DeathRecipient {
+        private final IBiometricPromptStatusListener mBiometricPromptStatusListener;
+
+        BiometricPromptStatusListener(IBiometricPromptStatusListener callback) {
+            mBiometricPromptStatusListener = callback;
+        }
+
+        void notifyBiometricPromptShowing() {
+            try {
+                mBiometricPromptStatusListener.onBiometricPromptShowing();
+            } catch (DeadObjectException e) {
+                Slog.w(TAG, "Death while invoking notifyHandleAuthenticate", e);
+                mBiometricPromptStatusListeners.remove(this);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to invoke notifyHandleAuthenticate", e);
+            }
+        }
+
+        void notifyBiometricPromptIdle() {
+            try {
+                mBiometricPromptStatusListener.onBiometricPromptIdle();
+            } catch (DeadObjectException e) {
+                Slog.w(TAG, "Death while invoking notifyDialogDismissed", e);
+                mBiometricPromptStatusListeners.remove(this);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to invoke notifyDialogDismissed", e);
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            Slog.e(TAG, "Biometric prompt callback binder died");
+            mBiometricPromptStatusListeners.remove(this);
+        }
+    }
+
     // Receives events from individual biometric sensors.
     private IBiometricSensorReceiver createBiometricSensorReceiver(final long requestId) {
         return new IBiometricSensorReceiver.Stub() {
@@ -705,6 +745,22 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
+        public void registerBiometricPromptStatusListener(IBiometricPromptStatusListener callback) {
+            super.registerBiometricPromptStatusListener_enforcePermission();
+
+            BiometricPromptStatusListener biometricPromptStatusListener =
+                    new BiometricPromptStatusListener(callback);
+            mBiometricPromptStatusListeners.add(biometricPromptStatusListener);
+
+            if (mAuthSession != null) {
+                biometricPromptStatusListener.notifyBiometricPromptShowing();
+            } else {
+                biometricPromptStatusListener.notifyBiometricPromptIdle();
+            }
+        }
+
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @Override // Binder call
         public void invalidateAuthenticatorIds(int userId, int fromSensorId,
                 IInvalidationCallback callback) {
 
@@ -1044,6 +1100,7 @@
         mDevicePolicyManager = mInjector.getDevicePolicyManager(context);
         mImpl = new BiometricServiceWrapper();
         mEnabledOnKeyguardCallbacks = new ArrayList<>();
+        mBiometricPromptStatusListeners = new ConcurrentLinkedQueue<>();
         mSettingObserver = mInjector.getSettingObserver(context, mHandler,
                 mEnabledOnKeyguardCallbacks);
         mRequestCounter = mInjector.getRequestGenerator();
@@ -1158,6 +1215,7 @@
             if (finished) {
                 Slog.d(TAG, "handleOnError: AuthSession finished");
                 mAuthSession = null;
+                notifyAuthSessionChanged();
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "RemoteException", e);
@@ -1186,6 +1244,7 @@
 
         session.onDialogDismissed(reason, credentialAttestation);
         mAuthSession = null;
+        notifyAuthSessionChanged();
     }
 
     private void handleOnTryAgainPressed(long requestId) {
@@ -1235,6 +1294,7 @@
         final boolean finished = session.onClientDied();
         if (finished) {
             mAuthSession = null;
+            notifyAuthSessionChanged();
         }
     }
 
@@ -1349,6 +1409,16 @@
         });
     }
 
+    private void notifyAuthSessionChanged() {
+        for (BiometricPromptStatusListener listener : mBiometricPromptStatusListeners) {
+            if (mAuthSession == null) {
+                listener.notifyBiometricPromptIdle();
+            } else {
+                listener.notifyBiometricPromptShowing();
+            }
+        }
+    }
+
     /**
      * handleAuthenticate() (above) which is called from BiometricPrompt determines which
      * modality/modalities to start authenticating with. authenticateInternal() should only be
@@ -1386,6 +1456,7 @@
         } catch (RemoteException e) {
             Slog.e(TAG, "RemoteException", e);
         }
+        notifyAuthSessionChanged();
     }
 
     private void handleCancelAuthentication(long requestId) {
@@ -1400,6 +1471,7 @@
         if (finished) {
             Slog.d(TAG, "handleCancelAuthentication: AuthSession finished");
             mAuthSession = null;
+            notifyAuthSessionChanged();
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index b537e0e..b12d831 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -6,3 +6,10 @@
   description: "This flag controls tunscany virtual HAL feature"
   bug: "294254230"
 }
+
+flag {
+    name: "de_hidl"
+    namespace: "biometrics_framework"
+    description: "feature flag for biometrics de-hidl"
+    bug: "287332354"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 78c95ad..a47135f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -35,6 +35,7 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+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;
@@ -116,7 +117,24 @@
 
     @LockoutTracker.LockoutMode
     public int handleFailedAttempt(int userId) {
-        return LockoutTracker.LOCKOUT_NONE;
+        if (Flags.deHidl()) {
+            if (mLockoutTracker != null) {
+                mLockoutTracker.addFailedAttemptForUser(getTargetUserId());
+            }
+            @LockoutTracker.LockoutMode final int lockoutMode =
+                    getLockoutTracker().getLockoutModeForUser(userId);
+            final PerformanceTracker performanceTracker =
+                    PerformanceTracker.getInstanceForSensorId(getSensorId());
+            if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
+                performanceTracker.incrementPermanentLockoutForUser(userId);
+            } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
+                performanceTracker.incrementTimedLockoutForUser(userId);
+            }
+
+            return lockoutMode;
+        } else {
+            return LockoutTracker.LOCKOUT_NONE;
+        }
     }
 
     protected long getStartTimeMs() {
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 8a54ae5..037ea38a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -586,4 +586,13 @@
             }
         }, 10000);
     }
+
+    /**
+     * Handle stop user client when user switching occurs.
+     */
+    public void onUserStopped() {}
+
+    public Handler getHandler() {
+        return mHandler;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java b/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java
index 95c4903..35e9bcb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java
@@ -32,6 +32,7 @@
         mUserLockoutStates = new SparseIntArray();
     }
 
+    @Override
     public void setLockoutModeForUser(int userId, @LockoutMode int mode) {
         Slog.d(TAG, "Lockout for user: " + userId +  " is " + mode);
         synchronized (this) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java b/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java
index 4a59c9d..8271380 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java
@@ -35,4 +35,7 @@
     @interface LockoutMode {}
 
     @LockoutMode int getLockoutModeForUser(int userId);
+    void setLockoutModeForUser(int userId, @LockoutMode int mode);
+    default void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {}
+    default void addFailedAttemptForUser(int userId) {}
 }
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 694dfd2..8075470 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -165,6 +165,7 @@
         }
     }
 
+    @Override
     public void onUserStopped() {
         if (mStopUserClient == null) {
             Slog.e(getTag(), "Unexpected onUserStopped");
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 cc00227..e5d4a63 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
@@ -31,6 +31,11 @@
         return mCurrentUserLockoutMode;
     }
 
+    @Override
+    public void setLockoutModeForUser(int userId, @LockoutMode int mode) {
+        setCurrentUserLockoutMode(mode);
+    }
+
     public void setCurrentUserLockoutMode(@LockoutMode int lockoutMode) {
         mCurrentUserLockoutMode = lockoutMode;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java
index 573c20f..d149f52 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java
@@ -38,7 +38,7 @@
 /**
  * Utilities for converting from hardware to framework-defined AIDL models.
  */
-final class AidlConversionUtils {
+public final class AidlConversionUtils {
 
     private static final String TAG = "AidlConversionUtils";
 
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
new file mode 100644
index 0000000..57f5b34
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
@@ -0,0 +1,319 @@
+/*
+ * 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.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.AuthenticationFrame;
+import android.hardware.biometrics.face.EnrollmentFrame;
+import android.hardware.biometrics.face.Error;
+import android.hardware.biometrics.face.ISessionCallback;
+import android.hardware.face.Face;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.util.Slog;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
+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.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.face.FaceUtils;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Response handler for the {@link ISessionCallback} HAL AIDL interface.
+ */
+public class AidlResponseHandler extends ISessionCallback.Stub {
+    /**
+     * Interface to send results to the AidlResponseHandler's owner.
+     */
+    public interface HardwareUnavailableCallback {
+        /**
+         * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+         */
+        void onHardwareUnavailable();
+    }
+
+    private static final String TAG = "AidlResponseHandler";
+
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final BiometricScheduler mScheduler;
+    private final int mSensorId;
+    private final int mUserId;
+    @NonNull
+    private final LockoutTracker mLockoutCache;
+    @NonNull
+    private final LockoutResetDispatcher mLockoutResetDispatcher;
+
+    @NonNull
+    private final AuthSessionCoordinator mAuthSessionCoordinator;
+    @NonNull
+    private final HardwareUnavailableCallback mHardwareUnavailableCallback;
+
+    public AidlResponseHandler(@NonNull Context context,
+            @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+            @NonNull LockoutTracker lockoutTracker,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull AuthSessionCoordinator authSessionCoordinator,
+            @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
+        mContext = context;
+        mScheduler = scheduler;
+        mSensorId = sensorId;
+        mUserId = userId;
+        mLockoutCache = lockoutTracker;
+        mLockoutResetDispatcher = lockoutResetDispatcher;
+        mAuthSessionCoordinator = authSessionCoordinator;
+        mHardwareUnavailableCallback = hardwareUnavailableCallback;
+    }
+
+    @Override
+    public int getInterfaceVersion() {
+        return this.VERSION;
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        return this.HASH;
+    }
+
+    @Override
+    public void onChallengeGenerated(long challenge) {
+        handleResponse(FaceGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(mSensorId,
+                mUserId, challenge), null);
+    }
+
+    @Override
+    public void onChallengeRevoked(long challenge) {
+        handleResponse(FaceRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(mSensorId,
+                mUserId, challenge), null);
+    }
+
+    @Override
+    public void onAuthenticationFrame(AuthenticationFrame frame) {
+        handleResponse(FaceAuthenticationClient.class, (c) -> {
+            if (frame == null) {
+                Slog.e(TAG, "Received null enrollment frame for face authentication client.");
+                return;
+            }
+            c.onAuthenticationFrame(AidlConversionUtils.toFrameworkAuthenticationFrame(frame));
+        }, null);
+    }
+
+    @Override
+    public void onEnrollmentFrame(EnrollmentFrame frame) {
+        handleResponse(FaceEnrollClient.class, (c) -> {
+            if (frame == null) {
+                Slog.e(TAG, "Received null enrollment frame for face enroll client.");
+                return;
+            }
+            c.onEnrollmentFrame(AidlConversionUtils.toFrameworkEnrollmentFrame(frame));
+        }, null);
+    }
+
+    @Override
+    public void onError(byte error, int vendorCode) {
+        onError(AidlConversionUtils.toFrameworkError(error), vendorCode);
+    }
+
+    /**
+     * Handle error messages from the HAL.
+     */
+    public void onError(int error, int vendorCode) {
+        handleResponse(ErrorConsumer.class, (c) -> {
+            c.onError(error, vendorCode);
+            if (error == Error.HW_UNAVAILABLE) {
+                mHardwareUnavailableCallback.onHardwareUnavailable();
+            }
+        }, null);
+    }
+
+    @Override
+    public void onEnrollmentProgress(int enrollmentId, int remaining) {
+        BaseClientMonitor client = mScheduler.getCurrentClient();
+        final int currentUserId;
+        if (client == null) {
+            return;
+        } else {
+            currentUserId = client.getTargetUserId();
+        }
+        final CharSequence name = FaceUtils.getInstance(mSensorId)
+                .getUniqueName(mContext, currentUserId);
+        final Face face = new Face(name, enrollmentId, mSensorId);
+
+        handleResponse(FaceEnrollClient.class, (c) -> c.onEnrollResult(face, remaining), null);
+    }
+
+    @Override
+    public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
+        final Face face = new Face("" /* name */, enrollmentId, mSensorId);
+        final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
+        final ArrayList<Byte> byteList = new ArrayList<>();
+        for (byte b : byteArray) {
+            byteList.add(b);
+        }
+        handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face,
+                true /* authenticated */, byteList), null);
+    }
+
+    @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);
+    }
+
+    @Override
+    public void onLockoutTimed(long durationMillis) {
+        handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis), null);
+    }
+
+    @Override
+    public void onLockoutPermanent() {
+        handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null);
+    }
+
+    @Override
+    public void onLockoutCleared() {
+        handleResponse(FaceResetLockoutClient.class, FaceResetLockoutClient::onLockoutCleared,
+                (c) -> FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
+                mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
+                Utils.getCurrentStrength(mSensorId), -1 /* requestId */));
+    }
+
+    @Override
+    public void onInteractionDetected() {
+        handleResponse(FaceDetectClient.class, FaceDetectClient::onInteractionDetected, null);
+    }
+
+    @Override
+    public void onEnrollmentsEnumerated(int[] enrollmentIds) {
+        if (enrollmentIds.length > 0) {
+            for (int i = 0; i < enrollmentIds.length; ++i) {
+                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);
+            }
+        } else {
+            handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(
+                    null /* identifier */, 0 /* remaining */), null);
+        }
+    }
+
+    @Override
+    public void onFeaturesRetrieved(byte[] features) {
+        handleResponse(FaceGetFeatureClient.class, (c) -> c.onFeatureGet(true /* success */,
+                features), null);
+    }
+
+    @Override
+    public void onFeatureSet(byte feature) {
+        handleResponse(FaceSetFeatureClient.class, (c) -> c.onFeatureSet(true /* success */), null);
+    }
+
+    @Override
+    public void onEnrollmentsRemoved(int[] enrollmentIds) {
+        if (enrollmentIds.length > 0) {
+            for (int i = 0; i < enrollmentIds.length; i++) {
+                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);
+            }
+        } else {
+            handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null /* identifier */,
+                    0 /* remaining */), null);
+        }
+    }
+
+    @Override
+    public void onAuthenticatorIdRetrieved(long authenticatorId) {
+        handleResponse(FaceGetAuthenticatorIdClient.class, (c) -> c.onAuthenticatorIdRetrieved(
+                authenticatorId), null);
+    }
+
+    @Override
+    public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
+        handleResponse(FaceInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated(
+                newAuthenticatorId), null);
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * Handles lockout changed messages sent by the HAL (specifically for HIDL HAL).
+     */
+    public void onLockoutChanged(long duration) {
+        mScheduler.getHandler().post(() -> {
+            @LockoutTracker.LockoutMode final int lockoutMode;
+            if (duration == 0) {
+                lockoutMode = LockoutTracker.LOCKOUT_NONE;
+            } else if (duration == -1 || duration == Long.MAX_VALUE) {
+                lockoutMode = LockoutTracker.LOCKOUT_PERMANENT;
+            } else {
+                lockoutMode = LockoutTracker.LOCKOUT_TIMED;
+            }
+
+            mLockoutCache.setLockoutModeForUser(mUserId, lockoutMode);
+
+            if (duration == 0) {
+                mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId);
+            }
+        });
+    }
+
+    private <T> void handleResponse(@NonNull Class<T> className,
+            @NonNull Consumer<T> actionIfClassMatchesClient,
+            @Nullable Consumer<BaseClientMonitor> alternateAction) {
+        mScheduler.getHandler().post(() -> {
+            final BaseClientMonitor client = mScheduler.getCurrentClient();
+            if (className.isInstance(client)) {
+                actionIfClassMatchesClient.accept((T) client);
+            } else {
+                Slog.d(TAG, "Current client is not an instance of " + className.getName());
+                if (alternateAction != null) {
+                    alternateAction.accept(client);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onSessionClosed() {
+        mScheduler.getHandler().post(mScheduler::onUserStopped);
+    }
+}
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 29eee6b..858bb86 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
@@ -16,10 +16,14 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
-import static com.android.server.biometrics.sensors.face.aidl.Sensor.HalSessionCallback;
-
 import android.annotation.NonNull;
+import android.content.Context;
 import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+
+import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter;
+
+import java.util.function.Supplier;
 
 /**
  * A holder for an AIDL {@link ISession} with additional metadata about the current user
@@ -31,14 +35,22 @@
     @NonNull
     private final ISession mSession;
     private final int mUserId;
-    @NonNull private final HalSessionCallback mHalSessionCallback;
+    @NonNull private final AidlResponseHandler mAidlResponseHandler;
 
     public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId,
-            HalSessionCallback halSessionCallback) {
+            AidlResponseHandler aidlResponseHandler) {
         mHalInterfaceVersion = halInterfaceVersion;
         mSession = session;
         mUserId = userId;
-        mHalSessionCallback = halSessionCallback;
+        mAidlResponseHandler = aidlResponseHandler;
+    }
+
+    public AidlSession(Context context, Supplier<IBiometricsFace> session, int userId,
+            AidlResponseHandler aidlResponseHandler) {
+        mSession = new AidlToHidlAdapter(context, session, userId, aidlResponseHandler);
+        mHalInterfaceVersion = 0;
+        mUserId = userId;
+        mAidlResponseHandler = aidlResponseHandler;
     }
 
     /** The underlying {@link ISession}. */
@@ -52,8 +64,8 @@
     }
 
     /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
-    HalSessionCallback getHalSessionCallback() {
-        return mHalSessionCallback;
+    AidlResponseHandler getHalSessionCallback() {
+        return mAidlResponseHandler;
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 35fc43a..470dc4b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -46,8 +46,8 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.face.UsageStats;
 
@@ -57,7 +57,8 @@
 /**
  * Face-specific authentication client for the {@link IFace} AIDL HAL interface.
  */
-class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAuthenticateOptions>
+public class FaceAuthenticationClient
+        extends AuthenticationClient<AidlSession, FaceAuthenticateOptions>
         implements LockoutConsumer {
     private static final String TAG = "FaceAuthenticationClient";
 
@@ -74,11 +75,11 @@
     @Nullable
     private ICancellationSignal mCancellationSignal;
     @Nullable
-    private SensorPrivacyManager mSensorPrivacyManager;
+    private final SensorPrivacyManager mSensorPrivacyManager;
     @FaceManager.FaceAcquired
     private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN;
 
-    FaceAuthenticationClient(@NonNull Context context,
+    public FaceAuthenticationClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, long operationId,
@@ -86,7 +87,7 @@
             boolean requireConfirmation,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull UsageStats usageStats,
-            @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
+            @NonNull LockoutTracker lockoutCache, boolean allowBackgroundAuthentication,
             @Authenticators.Types int sensorStrength) {
         this(context, lazyDaemon, token, requestId, listener, operationId,
                 restricted, options, cookie, requireConfirmation, logger, biometricContext,
@@ -103,12 +104,12 @@
             boolean requireConfirmation,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull UsageStats usageStats,
-            @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
+            @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
             SensorPrivacyManager sensorPrivacyManager,
             @Authenticators.Types int biometricStrength) {
         super(context, lazyDaemon, token, listener, operationId, restricted,
                 options, cookie, requireConfirmation, logger, biometricContext,
-                isStrongBiometric, null /* taskStackListener */, null /* lockoutCache */,
+                isStrongBiometric, null /* taskStackListener */, lockoutTracker,
                 allowBackgroundAuthentication, false /* shouldVibrate */,
                 biometricStrength);
         setRequestId(requestId);
@@ -263,8 +264,13 @@
         mLastAcquire = acquireInfo;
         final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
         onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
-        PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
-        pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
+
+        //Check if it is AIDL (lockoutTracker = null) or if it there is no lockout for HIDL
+        if (getLockoutTracker() == null || getLockoutTracker().getLockoutModeForUser(
+                getTargetUserId()) == LockoutTracker.LOCKOUT_NONE) {
+            PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
+            pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index f55cf05..dbed1f7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -85,7 +85,7 @@
                 }
             };
 
-    FaceEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+    public FaceEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId,
             @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
index 165c3a2..e404bd2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
@@ -36,7 +36,7 @@
 public class FaceGenerateChallengeClient extends GenerateChallengeClient<AidlSession> {
     private static final String TAG = "FaceGenerateChallengeClient";
 
-    FaceGenerateChallengeClient(@NonNull Context context,
+    public FaceGenerateChallengeClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
             int sensorId, @NonNull BiometricLogger logger,
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 ef3b345..c15049b 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
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.ISession;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.provider.Settings;
@@ -33,6 +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 java.util.HashMap;
 import java.util.Map;
@@ -46,14 +48,16 @@
     private static final String TAG = "FaceGetFeatureClient";
 
     private final int mUserId;
+    private final int mFeature;
 
-    FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+    public FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int sensorId, @NonNull BiometricLogger logger,
-            @NonNull BiometricContext biometricContext) {
+            @NonNull BiometricContext biometricContext, int feature) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
                 logger, biometricContext);
         mUserId = userId;
+        mFeature = feature;
     }
 
     @Override
@@ -70,7 +74,11 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().getSession().getFeatures();
+            ISession session = getFreshDaemon().getSession();
+            if (session instanceof AidlToHidlAdapter) {
+                ((AidlToHidlAdapter) session).setFeature(mFeature);
+            }
+            session.getFeatures();
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to getFeature", e);
             mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index f09d192..e75c6ab 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -38,9 +38,9 @@
 /**
  * Face-specific internal cleanup client for the {@link IFace} AIDL HAL interface.
  */
-class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> {
+public class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> {
 
-    FaceInternalCleanupClient(@NonNull Context context,
+    public FaceInternalCleanupClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
             int sensorId, @NonNull BiometricLogger logger,
             @NonNull BiometricContext biometricContext,
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 cc3118c..dd9c6d5 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
@@ -493,7 +493,7 @@
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
                             mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric,
-                    mUsageStats, mFaceSensors.get(sensorId).getLockoutCache(),
+                    mUsageStats, null /* lockoutTracker */,
                     allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
                 @Override
@@ -619,7 +619,7 @@
             final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext,
                     mFaceSensors.get(sensorId).getLazySession(), token, callback, userId,
                     mContext.getOpPackageName(), sensorId, BiometricLogger.ofUnknown(mContext),
-                    mBiometricContext);
+                    mBiometricContext, feature);
             scheduleForSensor(sensorId, client);
         });
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
index 0512017..0793888 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
@@ -36,12 +36,12 @@
 /**
  * Face-specific removal client for the {@link IFace} AIDL HAL interface.
  */
-class FaceRemovalClient extends RemovalClient<Face, AidlSession> {
+public class FaceRemovalClient extends RemovalClient<Face, AidlSession> {
     private static final String TAG = "FaceRemovalClient";
 
     final int[] mBiometricIds;
 
-    FaceRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+    public FaceRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int[] biometricIds, int userId, @NonNull String owner,
             @NonNull BiometricUtils<Face> utils, int sensorId,
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 1a12fcd..77b5592 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
@@ -32,7 +32,6 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.HalClientMonitor;
-import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 
@@ -48,14 +47,14 @@
     private static final String TAG = "FaceResetLockoutClient";
 
     private final HardwareAuthToken mHardwareAuthToken;
-    private final LockoutCache mLockoutCache;
+    private final LockoutTracker mLockoutCache;
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     private final int mBiometricStrength;
 
-    FaceResetLockoutClient(@NonNull Context context,
+    public FaceResetLockoutClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
+            @NonNull byte[] hardwareAuthToken, @NonNull LockoutTracker lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @Authenticators.Types int biometricStrength) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
@@ -107,7 +106,7 @@
      * be used instead.
      */
     static void resetLocalLockoutStateToNone(int sensorId, int userId,
-            @NonNull LockoutCache lockoutTracker,
+            @NonNull LockoutTracker lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull AuthSessionCoordinator authSessionCoordinator,
             @Authenticators.Types int biometricStrength, long requestId) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
index 8838345..0d6143a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
@@ -38,7 +38,7 @@
 
     private final long mChallenge;
 
-    FaceRevokeChallengeClient(@NonNull Context context,
+    public FaceRevokeChallengeClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
             int userId, @NonNull String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
index 6c14387..f6da872 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
@@ -46,7 +46,7 @@
     private final boolean mEnabled;
     private final HardwareAuthToken mHardwareAuthToken;
 
-    FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+    public FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
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 2ad41c2..54e66eb 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
@@ -23,16 +23,10 @@
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.biometrics.face.AuthenticationFrame;
-import android.hardware.biometrics.face.EnrollmentFrame;
-import android.hardware.biometrics.face.Error;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
-import android.hardware.biometrics.face.ISessionCallback;
-import android.hardware.face.Face;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.keymaster.HardwareAuthToken;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -44,29 +38,22 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
 import com.android.server.biometrics.Utils;
 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.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 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.RemovalConsumer;
 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.Map;
 import java.util.function.Supplier;
@@ -91,397 +78,6 @@
     @NonNull private final Supplier<AidlSession> mLazySession;
     @Nullable AidlSession mCurrentSession;
 
-    @VisibleForTesting
-    public static class HalSessionCallback extends ISessionCallback.Stub {
-        /**
-         * Interface to sends results to the HalSessionCallback's owner.
-         */
-        public interface Callback {
-            /**
-             * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
-             */
-            void onHardwareUnavailable();
-        }
-
-        @NonNull
-        private final Context mContext;
-        @NonNull
-        private final Handler mHandler;
-        @NonNull
-        private final String mTag;
-        @NonNull
-        private final UserAwareBiometricScheduler mScheduler;
-        private final int mSensorId;
-        private final int mUserId;
-        @NonNull
-        private final LockoutCache mLockoutCache;
-        @NonNull
-        private final LockoutResetDispatcher mLockoutResetDispatcher;
-
-        @NonNull
-        private AuthSessionCoordinator mAuthSessionCoordinator;
-        @NonNull
-        private final Callback mCallback;
-
-        HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
-                @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
-                @NonNull LockoutCache lockoutTracker,
-                @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-                @NonNull AuthSessionCoordinator authSessionCoordinator,
-                @NonNull Callback callback) {
-            mContext = context;
-            mHandler = handler;
-            mTag = tag;
-            mScheduler = scheduler;
-            mSensorId = sensorId;
-            mUserId = userId;
-            mLockoutCache = lockoutTracker;
-            mLockoutResetDispatcher = lockoutResetDispatcher;
-            mAuthSessionCoordinator = authSessionCoordinator;
-            mCallback = callback;
-        }
-
-        @Override
-        public int getInterfaceVersion() {
-            return this.VERSION;
-        }
-
-        @Override
-        public String getInterfaceHash() {
-            return this.HASH;
-        }
-
-        @Override
-        public void onChallengeGenerated(long challenge) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceGenerateChallengeClient)) {
-                    Slog.e(mTag, "onChallengeGenerated for wrong client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FaceGenerateChallengeClient generateChallengeClient =
-                        (FaceGenerateChallengeClient) client;
-                generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge);
-            });
-        }
-
-        @Override
-        public void onChallengeRevoked(long challenge) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceRevokeChallengeClient)) {
-                    Slog.e(mTag, "onChallengeRevoked for wrong client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FaceRevokeChallengeClient revokeChallengeClient =
-                        (FaceRevokeChallengeClient) client;
-                revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge);
-            });
-        }
-
-        @Override
-        public void onAuthenticationFrame(AuthenticationFrame frame) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceAuthenticationClient)) {
-                    Slog.e(mTag, "onAuthenticationFrame for incompatible client: "
-                            + Utils.getClientName(client));
-                    return;
-
-                }
-                if (frame == null) {
-                    Slog.e(mTag, "Received null authentication frame for client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-                ((FaceAuthenticationClient) client).onAuthenticationFrame(
-                        AidlConversionUtils.toFrameworkAuthenticationFrame(frame));
-            });
-        }
-
-        @Override
-        public void onEnrollmentFrame(EnrollmentFrame frame) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceEnrollClient)) {
-                    Slog.e(mTag, "onEnrollmentFrame for incompatible client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-                if (frame == null) {
-                    Slog.e(mTag, "Received null enrollment frame for client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-                ((FaceEnrollClient) client).onEnrollmentFrame(
-                        AidlConversionUtils.toFrameworkEnrollmentFrame(frame));
-            });
-        }
-
-        @Override
-        public void onError(byte error, int vendorCode) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                Slog.d(mTag, "onError"
-                        + ", client: " + Utils.getClientName(client)
-                        + ", error: " + error
-                        + ", vendorCode: " + vendorCode);
-                if (!(client instanceof ErrorConsumer)) {
-                    Slog.e(mTag, "onError for non-error consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final ErrorConsumer errorConsumer = (ErrorConsumer) client;
-                errorConsumer.onError(AidlConversionUtils.toFrameworkError(error), vendorCode);
-
-                if (error == Error.HW_UNAVAILABLE) {
-                    mCallback.onHardwareUnavailable();
-                }
-            });
-        }
-
-        @Override
-        public void onEnrollmentProgress(int enrollmentId, int remaining) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceEnrollClient)) {
-                    Slog.e(mTag, "onEnrollmentProgress for non-enroll client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final int currentUserId = client.getTargetUserId();
-                final CharSequence name = FaceUtils.getInstance(mSensorId)
-                        .getUniqueName(mContext, currentUserId);
-                final Face face = new Face(name, enrollmentId, mSensorId);
-
-                final FaceEnrollClient enrollClient = (FaceEnrollClient) client;
-                enrollClient.onEnrollResult(face, remaining);
-            });
-        }
-
-        @Override
-        public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof AuthenticationConsumer)) {
-                    Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final AuthenticationConsumer authenticationConsumer =
-                        (AuthenticationConsumer) client;
-                final Face face = new Face("" /* name */, enrollmentId, mSensorId);
-                final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
-                final ArrayList<Byte> byteList = new ArrayList<>();
-                for (byte b : byteArray) {
-                    byteList.add(b);
-                }
-                authenticationConsumer.onAuthenticated(face, true /* authenticated */, byteList);
-            });
-        }
-
-        @Override
-        public void onAuthenticationFailed() {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof AuthenticationConsumer)) {
-                    Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final AuthenticationConsumer authenticationConsumer =
-                        (AuthenticationConsumer) client;
-                final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId);
-                authenticationConsumer.onAuthenticated(face, false /* authenticated */,
-                        null /* hat */);
-            });
-        }
-
-        @Override
-        public void onLockoutTimed(long durationMillis) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof LockoutConsumer)) {
-                    Slog.e(mTag, "onLockoutTimed for non-lockout consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
-                lockoutConsumer.onLockoutTimed(durationMillis);
-            });
-        }
-
-        @Override
-        public void onLockoutPermanent() {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof LockoutConsumer)) {
-                    Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
-                lockoutConsumer.onLockoutPermanent();
-            });
-        }
-
-        @Override
-        public void onLockoutCleared() {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceResetLockoutClient)) {
-                    Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL");
-                    // Given that onLockoutCleared() can happen at any time, and is not necessarily
-                    // coming from a specific client, set this to -1 to indicate it wasn't for a
-                    // specific request.
-                    FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
-                            mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
-                            Utils.getCurrentStrength(mSensorId), -1 /* requestId */);
-                } else {
-                    Slog.d(mTag, "onLockoutCleared after resetLockout");
-                    final FaceResetLockoutClient resetLockoutClient =
-                            (FaceResetLockoutClient) client;
-                    resetLockoutClient.onLockoutCleared();
-                }
-            });
-        }
-
-        @Override
-        public void onInteractionDetected() {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceDetectClient)) {
-                    Slog.e(mTag, "onInteractionDetected for wrong client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FaceDetectClient detectClient = (FaceDetectClient) client;
-                detectClient.onInteractionDetected();
-            });
-        }
-
-        @Override
-        public void onEnrollmentsEnumerated(int[] enrollmentIds) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof EnumerateConsumer)) {
-                    Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final EnumerateConsumer enumerateConsumer =
-                        (EnumerateConsumer) client;
-                if (enrollmentIds.length > 0) {
-                    for (int i = 0; i < enrollmentIds.length; ++i) {
-                        final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
-                        enumerateConsumer.onEnumerationResult(face, enrollmentIds.length - i - 1);
-                    }
-                } else {
-                    enumerateConsumer.onEnumerationResult(null /* identifier */, 0 /* remaining */);
-                }
-            });
-        }
-
-        @Override
-        public void onFeaturesRetrieved(byte[] features) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceGetFeatureClient)) {
-                    Slog.e(mTag, "onFeaturesRetrieved for non-get feature consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-                final FaceGetFeatureClient faceGetFeatureClient = (FaceGetFeatureClient) client;
-                faceGetFeatureClient.onFeatureGet(true /* success */, features);
-            });
-
-        }
-
-        @Override
-        public void onFeatureSet(byte feature) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceSetFeatureClient)) {
-                    Slog.e(mTag, "onFeatureSet for non-set consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FaceSetFeatureClient faceSetFeatureClient = (FaceSetFeatureClient) client;
-                faceSetFeatureClient.onFeatureSet(true /* success */);
-            });
-        }
-
-        @Override
-        public void onEnrollmentsRemoved(int[] enrollmentIds) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof RemovalConsumer)) {
-                    Slog.e(mTag, "onRemoved for non-removal consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final RemovalConsumer removalConsumer = (RemovalConsumer) client;
-                if (enrollmentIds.length > 0) {
-                    for (int i = 0; i < enrollmentIds.length; i++) {
-                        final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
-                        removalConsumer.onRemoved(face, enrollmentIds.length - i - 1);
-                    }
-                } else {
-                    removalConsumer.onRemoved(null /* identifier */, 0 /* remaining */);
-                }
-            });
-        }
-
-        @Override
-        public void onAuthenticatorIdRetrieved(long authenticatorId) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceGetAuthenticatorIdClient)) {
-                    Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FaceGetAuthenticatorIdClient getAuthenticatorIdClient =
-                        (FaceGetAuthenticatorIdClient) client;
-                getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId);
-            });
-        }
-
-        @Override
-        public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FaceInvalidationClient)) {
-                    Slog.e(mTag, "onAuthenticatorIdInvalidated for wrong consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FaceInvalidationClient invalidationClient = (FaceInvalidationClient) client;
-                invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId);
-            });
-        }
-
-        @Override
-        public void onSessionClosed() {
-            mHandler.post(mScheduler::onUserStopped);
-        }
-    }
 
     Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
@@ -511,9 +107,9 @@
                     public StartUserClient<?, ?> getStartUserClient(int newUserId) {
                         final int sensorId = mSensorProperties.sensorId;
 
-                        final HalSessionCallback resultController = new HalSessionCallback(mContext,
-                                mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache,
-                                lockoutResetDispatcher,
+                        final AidlResponseHandler resultController = new AidlResponseHandler(
+                                mContext, mScheduler, sensorId, newUserId,
+                                mLockoutCache, lockoutResetDispatcher,
                                 biometricContext.getAuthSessionCoordinator(), () -> {
                             Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
                             mCurrentSession = null;
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/AidlToHidlAdapter.java
new file mode 100644
index 0000000..eecf44b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java
@@ -0,0 +1,347 @@
+/*
+ * 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.DurationMillisLong;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.face.EnrollmentStageConfig;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.biometrics.face.V1_0.OptionalBool;
+import android.hardware.biometrics.face.V1_0.Status;
+import android.hardware.common.NativeHandle;
+import android.hardware.face.Face;
+import android.hardware.face.FaceManager;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlConversionUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation.
+ */
+public class AidlToHidlAdapter implements ISession {
+
+    private final String TAG = "AidlToHidlAdapter";
+    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;
+    private final Clock mClock;
+    private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
+    @VisibleForTesting static final int ENROLL_TIMEOUT_SEC = 75;
+    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,
+            int userId, AidlResponseHandler aidlResponseHandler) {
+        this(context, session, userId, aidlResponseHandler, Clock.systemUTC());
+    }
+
+    AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session, int userId,
+            AidlResponseHandler aidlResponseHandler, Clock clock) {
+        mSession = session;
+        mUserId = userId;
+        mContext = context;
+        mClock = clock;
+        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();
+        if (isGeneratedChallengeCacheValid()) {
+            Slog.d(TAG, "Current challenge is cached and will be reused");
+            mHidlToAidlCallbackConverter.onChallengeGenerated(mGenerateChallengeResult);
+            return;
+        }
+        mGenerateChallengeCreatedAt = mClock.millis();
+        mGenerateChallengeResult = mSession.get().generateChallenge(CHALLENGE_TIMEOUT_SEC).value;
+        mHidlToAidlCallbackConverter.onChallengeGenerated(mGenerateChallengeResult);
+    }
+
+    @Override
+    public void revokeChallenge(long challenge) throws RemoteException {
+        final boolean shouldRevoke = decrementChallengeCount() == 0;
+        if (!shouldRevoke) {
+            Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: "
+                    + mGeneratedChallengeCount);
+            mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+                    BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
+            return;
+        }
+        mGenerateChallengeCreatedAt = INVALID_VALUE;
+        mGenerateChallengeResult = INVALID_VALUE;
+        mSession.get().revokeChallenge();
+        mHidlToAidlCallbackConverter.onChallengeRevoked(0L);
+    }
+
+    @Override
+    public EnrollmentStageConfig[] getEnrollmentConfig(byte enrollmentType) throws RemoteException {
+        //unsupported in HIDL
+        return null;
+    }
+
+    @Override
+    public ICancellationSignal enroll(HardwareAuthToken hat, byte type, byte[] features,
+            NativeHandle previewSurface) throws RemoteException {
+        final ArrayList<Byte> token = new ArrayList<>();
+        final byte[] hardwareAuthTokenArray = HardwareAuthTokenUtils.toByteArray(hat);
+        for (byte b : hardwareAuthTokenArray) {
+            token.add(b);
+        }
+        final ArrayList<Integer> disabledFeatures = new ArrayList<>();
+        for (byte b: features) {
+            disabledFeatures.add(AidlConversionUtils.convertAidlToFrameworkFeature(b));
+        }
+        mSession.get().enroll(token, ENROLL_TIMEOUT_SEC, disabledFeatures);
+        return new Cancellation();
+    }
+
+    @Override
+    public ICancellationSignal authenticate(long operationId) throws RemoteException {
+        mSession.get().authenticate(operationId);
+        return new Cancellation();
+    }
+
+    @Override
+    public ICancellationSignal detectInteraction() throws RemoteException {
+        mSession.get().authenticate(0);
+        return new Cancellation();
+    }
+
+    @Override
+    public void enumerateEnrollments() throws RemoteException {
+        mSession.get().enumerate();
+    }
+
+    @Override
+    public void removeEnrollments(int[] enrollmentIds) throws RemoteException {
+        mSession.get().remove(enrollmentIds[0]);
+    }
+
+    /**
+     * Needs to be called before getFeatures is invoked.
+     */
+    public void setFeature(int feature) {
+        mFeature = feature;
+    }
+
+    @Override
+    public void getFeatures() throws RemoteException {
+        final int faceId = getFaceId();
+        if (faceId == INVALID_VALUE || mFeature == INVALID_VALUE) {
+            return;
+        }
+
+        final OptionalBool result = mSession.get()
+                .getFeature(mFeature, faceId);
+
+        if (result.status == Status.OK && result.value) {
+            mHidlToAidlCallbackConverter.onFeatureGet(new byte[]{AidlConversionUtils
+                    .convertFrameworkToAidlFeature(mFeature)});
+        } else if (result.status == Status.OK) {
+            mHidlToAidlCallbackConverter.onFeatureGet(new byte[]{});
+        } else {
+            mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+                    BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0 /* vendorCode */);
+        }
+
+        mFeature = INVALID_VALUE;
+    }
+
+    @Override
+    public void setFeature(HardwareAuthToken hat, byte feature, boolean enabled)
+            throws RemoteException {
+        final int faceId = getFaceId();
+        if (faceId == INVALID_VALUE) {
+            return;
+        }
+        ArrayList<Byte> hardwareAuthTokenList = new ArrayList<>();
+        for (byte b: HardwareAuthTokenUtils.toByteArray(hat)) {
+            hardwareAuthTokenList.add(b);
+        }
+        final int result = mSession.get().setFeature(
+                AidlConversionUtils.convertAidlToFrameworkFeature(feature),
+                enabled, hardwareAuthTokenList, faceId);
+        if (result == Status.OK) {
+            mHidlToAidlCallbackConverter.onFeatureSet(feature);
+        } else {
+            mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+                    BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0 /* vendorCode */);
+        }
+    }
+
+    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;
+        mHidlToAidlCallbackConverter.onAuthenticatorIdRetrieved(authenticatorId);
+    }
+
+    @Override
+    public void invalidateAuthenticatorId() throws RemoteException {
+        //unsupported in HIDL
+    }
+
+    @Override
+    public void resetLockout(HardwareAuthToken hat) throws RemoteException {
+        ArrayList<Byte> hardwareAuthToken = new ArrayList<>();
+        for (byte b : HardwareAuthTokenUtils.toByteArray(hat)) {
+            hardwareAuthToken.add(b);
+        }
+        mSession.get().resetLockout(hardwareAuthToken);
+    }
+
+    @Override
+    public void close() throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public ICancellationSignal authenticateWithContext(long operationId, OperationContext context)
+            throws RemoteException {
+        //Unsupported in HIDL
+        return null;
+    }
+
+    @Override
+    public ICancellationSignal enrollWithContext(HardwareAuthToken hat, byte type, byte[] features,
+            NativeHandle previewSurface, OperationContext context) throws RemoteException {
+        //Unsupported in HIDL
+        return null;
+    }
+
+    @Override
+    public ICancellationSignal detectInteractionWithContext(OperationContext context)
+            throws RemoteException {
+        //Unsupported in HIDL
+        return null;
+    }
+
+    @Override
+    public void onContextChanged(OperationContext context) throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public int getInterfaceVersion() throws RemoteException {
+        //Unsupported in HIDL
+        return 0;
+    }
+
+    @Override
+    public String getInterfaceHash() throws RemoteException {
+        //Unsupported in HIDL
+        return null;
+    }
+
+    /**
+     * Cancellation in HIDL occurs for the entire session, instead of a specific client.
+     */
+    private class Cancellation extends ICancellationSignal.Stub {
+
+        Cancellation() {}
+        @Override
+        public void cancel() throws RemoteException {
+            try {
+                mSession.get().cancel();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception when requesting cancel", e);
+            }
+        }
+
+        @Override
+        public int getInterfaceVersion() throws RemoteException {
+            return 0;
+        }
+
+        @Override
+        public String getInterfaceHash() throws RemoteException {
+            return null;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 1499317..46ce0b6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -54,6 +54,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
 import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
@@ -61,6 +62,7 @@
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -78,6 +80,8 @@
 import com.android.server.biometrics.sensors.face.LockoutHalImpl;
 import com.android.server.biometrics.sensors.face.ServiceProvider;
 import com.android.server.biometrics.sensors.face.UsageStats;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.face.aidl.AidlSession;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -131,7 +135,9 @@
     private int mCurrentUserId = UserHandle.USER_NULL;
     private final int mSensorId;
     private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
+    private final LockoutResetDispatcher mLockoutResetDispatcher;
     private FaceGenerateChallengeClient mGeneratedChallengeCache = null;
+    private AidlSession mSession;
 
     private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
         @Override
@@ -361,6 +367,7 @@
         mLockoutTracker = new LockoutHalImpl();
         mHalResultController = new HalResultController(sensorProps.sensorId, context, mHandler,
                 mScheduler, mLockoutTracker, lockoutResetDispatcher);
+        mLockoutResetDispatcher = lockoutResetDispatcher;
         mHalResultController.setCallback(() -> {
             mDaemon = null;
             mCurrentUserId = UserHandle.USER_NULL;
@@ -420,6 +427,24 @@
         });
     }
 
+    public int getCurrentUserId() {
+        return mCurrentUserId;
+    }
+
+    synchronized AidlSession getSession() {
+        if (mDaemon != null && mSession != null) {
+            return mSession;
+        } else {
+            return mSession = new AidlSession(mContext, this::getDaemon, mCurrentUserId,
+                    new AidlResponseHandler(mContext, mScheduler, mSensorId,
+                            mCurrentUserId, mLockoutTracker, mLockoutResetDispatcher,
+                            new AuthSessionCoordinator(), () -> {
+                        mDaemon = null;
+                        mCurrentUserId = UserHandle.USER_NULL;
+                    }));
+        }
+    }
+
     private synchronized IBiometricsFace getDaemon() {
         if (mTestHalEnabled) {
             final TestHal testHal = new TestHal(mContext, mSensorId);
@@ -551,32 +576,63 @@
     public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
         mHandler.post(() -> {
-            incrementChallengeCount();
-
-            if (isGeneratedChallengeCacheValid()) {
-                Slog.d(TAG, "Current challenge is cached and will be reused");
-                mGeneratedChallengeCache.reuseResult(receiver);
-                return;
-            }
-
             scheduleUpdateActiveUserWithoutHandler(userId);
 
-            final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
-                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
-                    opPackageName, mSensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext, sSystemClock.millis());
-            mGeneratedChallengeCache = client;
-            mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-                @Override
-                public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-                    if (client != clientMonitor) {
-                        Slog.e(TAG, "scheduleGenerateChallenge onClientStarted, mismatched client."
-                                + " Expecting: " + client + ", received: " + clientMonitor);
-                    }
+            if (Flags.deHidl()) {
+                scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName);
+            } else {
+                scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName);
+            }
+        });
+    }
+
+    private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+        final com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient client =
+                new com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient(
+                        mContext, this::getSession, token,
+                        new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+                        mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                        mBiometricContext);
+        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+            @Override
+            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                if (client != clientMonitor) {
+                    Slog.e(TAG,
+                            "scheduleGenerateChallenge onClientStarted, mismatched client."
+                                    + " Expecting: " + client + ", received: "
+                                    + clientMonitor);
                 }
-            });
+            }
+        });
+    }
+
+    private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+        incrementChallengeCount();
+        if (isGeneratedChallengeCacheValid()) {
+            Slog.d(TAG, "Current challenge is cached and will be reused");
+            mGeneratedChallengeCache.reuseResult(receiver);
+            return;
+        }
+
+        final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
+                mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
+                opPackageName, mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                mBiometricContext, sSystemClock.millis());
+        mGeneratedChallengeCache = client;
+        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+            @Override
+            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                if (client != clientMonitor) {
+                    Slog.e(TAG,
+                            "scheduleGenerateChallenge onClientStarted, mismatched client."
+                                    + " Expecting: " + client + ", received: "
+                                    + clientMonitor);
+                }
+            }
         });
     }
 
@@ -584,31 +640,62 @@
     public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull String opPackageName, long challenge) {
         mHandler.post(() -> {
-            final boolean shouldRevoke = decrementChallengeCount() == 0;
-            if (!shouldRevoke) {
-                Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: "
-                        + mGeneratedChallengeCount);
-                return;
+            if (Flags.deHidl()) {
+                scheduleRevokeChallengeAidl(userId, token, opPackageName);
+            } else {
+                scheduleRevokeChallengeHidl(userId, token, opPackageName);
             }
+        });
+    }
 
-            Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients");
-            mGeneratedChallengeCache = null;
-
-            final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
-                    mLazyDaemon, token, userId, opPackageName, mSensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext);
-            mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-                @Override
-                public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                        boolean success) {
-                    if (client != clientMonitor) {
-                        Slog.e(TAG, "scheduleRevokeChallenge, mismatched client."
-                                + "Expecting: " + client + ", received: " + clientMonitor);
-                    }
+    private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token,
+            @NonNull String opPackageName) {
+        final com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient
+                client =
+                new com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient(
+                        mContext, this::getSession, token, userId, opPackageName, mSensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector), mBiometricContext, 0L);
+        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                if (client != clientMonitor) {
+                    Slog.e(TAG,
+                            "scheduleRevokeChallenge, mismatched client." + "Expecting: "
+                                    + client + ", received: " + clientMonitor);
                 }
-            });
+            }
+        });
+    }
+
+    private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token,
+            @NonNull String opPackageName) {
+        final boolean shouldRevoke = decrementChallengeCount() == 0;
+        if (!shouldRevoke) {
+            Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: "
+                    + mGeneratedChallengeCount);
+            return;
+        }
+
+        Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients");
+        mGeneratedChallengeCache = null;
+        final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
+                mLazyDaemon, token, userId, opPackageName, mSensorId,
+                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                mBiometricContext);
+        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                if (client != clientMonitor) {
+                    Slog.e(TAG,
+                            "scheduleRevokeChallenge, mismatched client." + "Expecting: "
+                                    + client + ", received: " + clientMonitor);
+                }
+            }
         });
     }
 
@@ -620,7 +707,62 @@
         final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
+            if (Flags.deHidl()) {
+                scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver,
+                        opPackageName, disabledFeatures, previewSurface, id);
+            } else {
+                scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver,
+                        opPackageName, disabledFeatures, previewSurface, id);
+            }
+        });
+        return id;
+    }
 
+    private void scheduleEnrollAidl(@NonNull IBinder token,
+            @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
+            @NonNull String opPackageName, @NonNull int[] disabledFeatures,
+            @Nullable Surface previewSurface, long id) {
+        final com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient client =
+                new com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient(
+                        mContext, this::getSession, token,
+                        new ClientMonitorCallbackConverter(receiver), userId,
+                        hardwareAuthToken, opPackageName, id,
+                        FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
+                        ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector), mBiometricContext,
+                        mContext.getResources().getInteger(
+                                com.android.internal.R.integer.config_faceMaxTemplatesPerUser),
+                        false);
+
+        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+            @Override
+            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                mBiometricStateCallback.onClientStarted(clientMonitor);
+            }
+
+            @Override
+            public void onBiometricAction(int action) {
+                mBiometricStateCallback.onBiometricAction(action);
+            }
+
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                mBiometricStateCallback.onClientFinished(clientMonitor, success);
+                if (success) {
+                    // Update authenticatorIds
+                    scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId());
+                }
+            }
+        });
+    }
+
+    private void scheduleEnrollHidl(@NonNull IBinder token,
+            @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
+            @NonNull String opPackageName, @NonNull int[] disabledFeatures,
+            @Nullable Surface previewSurface, long id) {
             final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                     opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
@@ -628,7 +770,6 @@
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
                             BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext);
-
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
                 public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
@@ -650,8 +791,6 @@
                     }
                 }
             });
-        });
-        return id;
     }
 
     @Override
@@ -683,18 +822,46 @@
             scheduleUpdateActiveUserWithoutHandler(userId);
 
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
-            final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
-                    mLazyDaemon, token, requestId, receiver, operationId, restricted,
-                    options, cookie, false /* requireConfirmation */,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
-                            mAuthenticationStatsCollector),
-                    mBiometricContext, isStrongBiometric, mLockoutTracker,
-                    mUsageStats, allowBackgroundAuthentication,
-                    Utils.getCurrentStrength(mSensorId));
-            mScheduler.scheduleClientMonitor(client);
+            if (Flags.deHidl()) {
+                scheduleAuthenticateAidl(token, operationId, cookie, receiver, options, requestId,
+                        restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
+            } else {
+                scheduleAuthenticateHidl(token, operationId, cookie, receiver, options, requestId,
+                        restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
+            }
         });
     }
 
+    private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId,
+            int cookie, @NonNull ClientMonitorCallbackConverter receiver,
+            @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
+            int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
+        final com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient
+                client =
+                new com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient(
+                        mContext, this::getSession, token, requestId, receiver, operationId,
+                        restricted, options, cookie, false /* requireConfirmation */,
+                        createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                                mAuthenticationStatsCollector), mBiometricContext,
+                        isStrongBiometric, mUsageStats, mLockoutTracker,
+                        allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+        mScheduler.scheduleClientMonitor(client);
+    }
+
+    private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId,
+            int cookie, @NonNull ClientMonitorCallbackConverter receiver,
+            @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
+            int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
+        final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
+                mLazyDaemon, token, requestId, receiver, operationId, restricted, options,
+                cookie, false /* requireConfirmation */,
+                createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                        mAuthenticationStatsCollector), mBiometricContext,
+                isStrongBiometric, mLockoutTracker, mUsageStats,
+                allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+        mScheduler.scheduleClientMonitor(client);
+    }
+
     @Override
     public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter receiver,
@@ -719,13 +886,11 @@
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
-            final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
-                    new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
-                    FaceUtils.getLegacyInstance(mSensorId), mSensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext, mAuthenticatorIds);
-            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+            if (Flags.deHidl()) {
+                scheduleRemoveAidl(token, userId, receiver, opPackageName, faceId);
+            } else {
+                scheduleRemoveHidl(token, userId, receiver, opPackageName, faceId);
+            }
         });
     }
 
@@ -736,17 +901,39 @@
             scheduleUpdateActiveUserWithoutHandler(userId);
 
             // For IBiometricsFace@1.0, remove(0) means remove all enrollments
-            final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
-                    new ClientMonitorCallbackConverter(receiver), 0 /* faceId */, userId,
-                    opPackageName,
-                    FaceUtils.getLegacyInstance(mSensorId), mSensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext, mAuthenticatorIds);
-            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+            if (Flags.deHidl()) {
+                scheduleRemoveAidl(token, userId, receiver, opPackageName, 0);
+            } else {
+                scheduleRemoveHidl(token, userId, receiver, opPackageName, 0);
+            }
         });
     }
 
+    private void scheduleRemoveAidl(@NonNull IBinder token, int userId,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) {
+        final com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient client =
+                new com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient(
+                        mContext, this::getSession, token,
+                        new ClientMonitorCallbackConverter(receiver), new int[]{faceId}, userId,
+                        opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector), mBiometricContext,
+                        mAuthenticatorIds);
+        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+    }
+
+    private void scheduleRemoveHidl(@NonNull IBinder token, int userId,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) {
+        final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
+                new ClientMonitorCallbackConverter(receiver), faceId, userId,
+                opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId,
+                createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                mBiometricContext, mAuthenticatorIds);
+        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+    }
+
     @Override
     public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
         mHandler.post(() -> {
@@ -756,89 +943,180 @@
             }
 
             scheduleUpdateActiveUserWithoutHandler(userId);
-
-            final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
-                    mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext, hardwareAuthToken);
-            mScheduler.scheduleClientMonitor(client);
+            if (Flags.deHidl()) {
+                scheduleResetLockoutAidl(userId, hardwareAuthToken);
+            } else {
+                scheduleResetLockoutHidl(userId, hardwareAuthToken);
+            }
         });
     }
 
+    private void scheduleResetLockoutAidl(int userId,
+            @NonNull byte[] hardwareAuthToken) {
+        final com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient client =
+                new com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient(
+                        mContext, this::getSession, userId, mContext.getOpPackageName(),
+                        mSensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector),
+                        mBiometricContext, hardwareAuthToken, mLockoutTracker,
+                        mLockoutResetDispatcher, mSensorProperties.sensorStrength);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
+    private void scheduleResetLockoutHidl(int userId,
+            @NonNull byte[] hardwareAuthToken) {
+        final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
+                mLazyDaemon,
+                userId, mContext.getOpPackageName(), mSensorId,
+                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                mBiometricContext, hardwareAuthToken);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
     @Override
     public void scheduleSetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
             boolean enabled, @NonNull byte[] hardwareAuthToken,
             @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
         mHandler.post(() -> {
-            final List<Face> faces = getEnrolledFaces(sensorId, userId);
-            if (faces.isEmpty()) {
-                Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId);
-                return;
-            }
-
             scheduleUpdateActiveUserWithoutHandler(userId);
-
-            final int faceId = faces.get(0).getBiometricId();
-            final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
-                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
-                    opPackageName, mSensorId, BiometricLogger.ofUnknown(mContext),
-                    mBiometricContext,
-                    feature, enabled, hardwareAuthToken, faceId);
-            mScheduler.scheduleClientMonitor(client);
+            if (Flags.deHidl()) {
+                scheduleSetFeatureAidl(sensorId, token, userId, feature, enabled, hardwareAuthToken,
+                        receiver, opPackageName);
+            } else {
+                scheduleSetFeatureHidl(sensorId, token, userId, feature, enabled, hardwareAuthToken,
+                        receiver, opPackageName);
+            }
         });
     }
 
+    private void scheduleSetFeatureHidl(int sensorId, @NonNull IBinder token, int userId,
+            int feature, boolean enabled, @NonNull byte[] hardwareAuthToken,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+        final List<Face> faces = getEnrolledFaces(sensorId, userId);
+        if (faces.isEmpty()) {
+            Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId);
+            return;
+        }
+        final int faceId = faces.get(0).getBiometricId();
+        final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext, mLazyDaemon,
+                token, new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+                mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, feature,
+                enabled, hardwareAuthToken, faceId);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
+    private void scheduleSetFeatureAidl(int sensorId, @NonNull IBinder token, int userId,
+            int feature, boolean enabled, @NonNull byte[] hardwareAuthToken,
+            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+        final com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient client =
+                new com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient(
+                        mContext, this::getSession, token,
+                        new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+                        mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext,
+                        feature, enabled, hardwareAuthToken);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
+
     @Override
     public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
             @Nullable ClientMonitorCallbackConverter listener, @NonNull String opPackageName) {
         mHandler.post(() -> {
-            final List<Face> faces = getEnrolledFaces(sensorId, userId);
-            if (faces.isEmpty()) {
-                Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId);
-                return;
-            }
-
             scheduleUpdateActiveUserWithoutHandler(userId);
 
-            final int faceId = faces.get(0).getBiometricId();
-            final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon,
-                    token, listener, userId, opPackageName, mSensorId,
-                    BiometricLogger.ofUnknown(mContext), mBiometricContext,
-                    feature, faceId);
-            mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-                @Override
-                public void onClientFinished(
-                        @NonNull BaseClientMonitor clientMonitor, boolean success) {
-                    if (success && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) {
-                        final int settingsValue = client.getValue() ? 1 : 0;
-                        Slog.d(TAG, "Updating attention value for user: " + userId
-                                + " to value: " + settingsValue);
-                        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                                Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED,
-                                settingsValue, userId);
-                    }
-                }
-            });
+            if (Flags.deHidl()) {
+                scheduleGetFeatureAidl(token, userId, feature, listener,
+                        opPackageName);
+            } else {
+                scheduleGetFeatureHidl(sensorId, token, userId, feature, listener,
+                        opPackageName);
+            }
         });
     }
 
+    private void scheduleGetFeatureHidl(int sensorId, @NonNull IBinder token, int userId,
+            int feature, @Nullable ClientMonitorCallbackConverter listener,
+            @NonNull String opPackageName) {
+        final List<Face> faces = getEnrolledFaces(sensorId, userId);
+        if (faces.isEmpty()) {
+            Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId);
+            return;
+        }
+
+        final int faceId = faces.get(0).getBiometricId();
+        final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon,
+                token, listener, userId, opPackageName, mSensorId,
+                BiometricLogger.ofUnknown(mContext), mBiometricContext, feature, faceId);
+        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                if (success
+                        && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) {
+                    final int settingsValue = client.getValue() ? 1 : 0;
+                    Slog.d(TAG,
+                            "Updating attention value for user: " + userId + " to value: "
+                                    + settingsValue);
+                    Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                            Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, settingsValue,
+                            userId);
+                }
+            }
+        });
+    }
+
+    private void scheduleGetFeatureAidl(@NonNull IBinder token, int userId,
+            int feature, @Nullable ClientMonitorCallbackConverter listener,
+            @NonNull String opPackageName) {
+        final com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient client =
+                new com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient(
+                        mContext, this::getSession, token, listener, userId, opPackageName,
+                        mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext,
+                        feature);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
     private void scheduleInternalCleanup(int userId,
             @Nullable ClientMonitorCallback callback) {
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
-
-            final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
-                    mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext,
-                    FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
-            mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback,
-                    mBiometricStateCallback));
+            if (Flags.deHidl()) {
+                scheduleInternalCleanupAidl(userId, callback);
+            } else {
+                scheduleInternalCleanupHidl(userId, callback);
+            }
         });
     }
 
+    private void scheduleInternalCleanupHidl(int userId,
+            @Nullable ClientMonitorCallback callback) {
+        final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
+                mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
+                createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                mBiometricContext, FaceUtils.getLegacyInstance(mSensorId),
+                mAuthenticatorIds);
+        mScheduler.scheduleClientMonitor(client,
+                new ClientMonitorCompositeCallback(callback, mBiometricStateCallback));
+    }
+
+    private void scheduleInternalCleanupAidl(int userId,
+            @Nullable ClientMonitorCallback callback) {
+        final com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient
+                client =
+                new com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient(
+                        mContext, this::getSession, userId, mContext.getOpPackageName(),
+                        mSensorId, createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                        mBiometricContext, FaceUtils.getLegacyInstance(mSensorId),
+                        mAuthenticatorIds);
+        mScheduler.scheduleClientMonitor(client,
+                new ClientMonitorCompositeCallback(callback, mBiometricStateCallback));
+    }
+
     @Override
     public void scheduleInternalCleanup(int sensorId, int userId,
             @Nullable ClientMonitorCallback callback) {
@@ -970,6 +1248,10 @@
             public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
                     boolean success) {
                 if (success) {
+                    if (mCurrentUserId != targetUserId) {
+                        // Create new session with updated user ID
+                        mSession = null;
+                    }
                     mCurrentUserId = targetUserId;
                 } else {
                     Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId);
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
new file mode 100644
index 0000000..36a9790
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
@@ -0,0 +1,115 @@
+/*
+ * 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.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+
+import java.util.ArrayList;
+
+/**
+ * Convert HIDL-specific callback interface {@link IBiometricsFaceClientCallback} to AIDL
+ * response handler.
+ */
+public class HidlToAidlCallbackConverter extends IBiometricsFaceClientCallback.Stub {
+
+    private final AidlResponseHandler mAidlResponseHandler;
+
+    public HidlToAidlCallbackConverter(AidlResponseHandler aidlResponseHandler) {
+        mAidlResponseHandler = aidlResponseHandler;
+    }
+
+    @Override
+    public void onEnrollResult(
+            long deviceId, int faceId, int userId, int remaining) {
+        mAidlResponseHandler.onEnrollmentProgress(faceId, remaining);
+    }
+
+    @Override
+    public void onAuthenticated(long deviceId, int faceId, int userId,
+            ArrayList<Byte> token) {
+        final boolean authenticated = faceId != 0;
+        byte[] hardwareAuthToken = new byte[token.size()];
+
+        for (int i = 0; i < token.size(); i++) {
+            hardwareAuthToken[i] = token.get(i);
+        }
+
+        if (authenticated) {
+            mAidlResponseHandler.onAuthenticationSucceeded(faceId,
+                    HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken));
+        } else {
+            mAidlResponseHandler.onAuthenticationFailed();
+        }
+    }
+
+    @Override
+    public void onAcquired(long deviceId, int userId, int acquiredInfo,
+            int vendorCode) {
+        mAidlResponseHandler.onAcquired(acquiredInfo, vendorCode);
+    }
+
+    @Override
+    public void onError(long deviceId, int userId, int error, int vendorCode) {
+        mAidlResponseHandler.onError(error, vendorCode);
+    }
+
+    @Override
+    public void onRemoved(long deviceId, ArrayList<Integer> removed, int userId) {
+        int[] enrollmentIds = new int[removed.size()];
+        for (int i = 0; i < removed.size(); i++) {
+            enrollmentIds[i] = removed.get(i);
+        }
+        mAidlResponseHandler.onEnrollmentsRemoved(enrollmentIds);
+    }
+
+    @Override
+    public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId) {
+        int[] enrollmentIds = new int[faceIds.size()];
+        for (int i = 0; i < faceIds.size(); i++) {
+            enrollmentIds[i] = faceIds.get(i);
+        }
+        mAidlResponseHandler.onEnrollmentsEnumerated(enrollmentIds);
+    }
+
+    @Override
+    public void onLockoutChanged(long duration) {
+        mAidlResponseHandler.onLockoutChanged(duration);
+    }
+
+    void onChallengeGenerated(long challenge) {
+        mAidlResponseHandler.onChallengeGenerated(challenge);
+    }
+
+    void onChallengeRevoked(long challenge) {
+        mAidlResponseHandler.onChallengeRevoked(challenge);
+    }
+
+    void onFeatureGet(byte[] features) {
+        mAidlResponseHandler.onFeaturesRetrieved(features);
+    }
+
+    void onFeatureSet(byte feature) {
+        mAidlResponseHandler.onFeatureSet(feature);
+    }
+
+    void onAuthenticatorIdRetrieved(long authenticatorId) {
+        mAidlResponseHandler.onAuthenticatorIdRetrieved(authenticatorId);
+    }
+}
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
new file mode 100644
index 0000000..4a01943
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
@@ -0,0 +1,274 @@
+/*
+ * 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.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.Error;
+import android.hardware.biometrics.fingerprint.ISessionCallback;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.util.Slog;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
+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.RemovalConsumer;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+
+/**
+ * Response handler for the {@link ISessionCallback} HAL AIDL interface.
+ */
+public class AidlResponseHandler extends ISessionCallback.Stub {
+
+    /**
+     * Interface to send results to the AidlResponseHandler's owner.
+     */
+    public interface HardwareUnavailableCallback {
+        /**
+         * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+         */
+        void onHardwareUnavailable();
+    }
+
+    private static final String TAG = "AidlResponseHandler";
+
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final BiometricScheduler mScheduler;
+    private final int mSensorId;
+    private final int mUserId;
+    @NonNull
+    private final LockoutCache mLockoutCache;
+    @NonNull
+    private final LockoutResetDispatcher mLockoutResetDispatcher;
+    @NonNull
+    private final AuthSessionCoordinator mAuthSessionCoordinator;
+    @NonNull
+    private final HardwareUnavailableCallback mHardwareUnavailableCallback;
+
+    public AidlResponseHandler(@NonNull Context context,
+            @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+            @NonNull LockoutCache lockoutTracker,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull AuthSessionCoordinator authSessionCoordinator,
+            @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
+        mContext = context;
+        mScheduler = scheduler;
+        mSensorId = sensorId;
+        mUserId = userId;
+        mLockoutCache = lockoutTracker;
+        mLockoutResetDispatcher = lockoutResetDispatcher;
+        mAuthSessionCoordinator = authSessionCoordinator;
+        mHardwareUnavailableCallback = hardwareUnavailableCallback;
+    }
+
+    @Override
+    public int getInterfaceVersion() {
+        return this.VERSION;
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        return this.HASH;
+    }
+
+    @Override
+    public void onChallengeGenerated(long challenge) {
+        handleResponse(FingerprintGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(
+                mSensorId, mUserId, challenge), null);
+    }
+
+    @Override
+    public void onChallengeRevoked(long challenge) {
+        handleResponse(FingerprintRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(
+                challenge), null);
+    }
+
+    /**
+     * 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);
+    }
+
+    @Override
+    public void onAcquired(byte info, int vendorCode) {
+        handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(
+                AidlConversionUtils.toFrameworkAcquiredInfo(info), vendorCode), null);
+    }
+
+    /**
+     * Handle error messages from the HAL.
+     */
+    public void onError(int error, int vendorCode) {
+        handleResponse(ErrorConsumer.class, (c) -> {
+            c.onError(error, vendorCode);
+            if (error == Error.HW_UNAVAILABLE) {
+                mHardwareUnavailableCallback.onHardwareUnavailable();
+            }
+        }, null);
+    }
+
+    @Override
+    public void onError(byte error, int vendorCode) {
+        onError(AidlConversionUtils.toFrameworkError(error), vendorCode);
+    }
+
+    @Override
+    public void onEnrollmentProgress(int enrollmentId, int remaining) {
+        BaseClientMonitor client = mScheduler.getCurrentClient();
+        final int currentUserId;
+        if (client == null) {
+            return;
+        } else {
+            currentUserId = client.getTargetUserId();
+        }
+        final CharSequence name = FingerprintUtils.getInstance(mSensorId)
+                .getUniqueName(mContext, currentUserId);
+        final Fingerprint fingerprint = new Fingerprint(name, currentUserId,
+                enrollmentId, mSensorId);
+        handleResponse(FingerprintEnrollClient.class, (c) -> c.onEnrollResult(fingerprint,
+                remaining), null);
+    }
+
+    @Override
+    public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
+        final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
+        final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
+        final ArrayList<Byte> byteList = new ArrayList<>();
+        for (byte b : byteArray) {
+            byteList.add(b);
+        }
+        handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(fp,
+                true /* authenticated */, byteList), (c) -> onInteractionDetected());
+    }
+
+    @Override
+    public void onAuthenticationFailed() {
+        final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId);
+        handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(fp,
+                false /* authenticated */, null /* hardwareAuthToken */),
+                (c) -> onInteractionDetected());
+    }
+
+    @Override
+    public void onLockoutTimed(long durationMillis) {
+        handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis),
+                null);
+    }
+
+    @Override
+    public void onLockoutPermanent() {
+        handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null);
+    }
+
+    @Override
+    public void onLockoutCleared() {
+        handleResponse(FingerprintResetLockoutClient.class,
+                FingerprintResetLockoutClient::onLockoutCleared,
+                (c) -> FingerprintResetLockoutClient.resetLocalLockoutStateToNone(
+                        mSensorId, mUserId, mLockoutCache, mLockoutResetDispatcher,
+                        mAuthSessionCoordinator, Utils.getCurrentStrength(mSensorId),
+                        -1 /* requestId */));
+    }
+
+    @Override
+    public void onInteractionDetected() {
+        handleResponse(FingerprintDetectClient.class,
+                FingerprintDetectClient::onInteractionDetected, null);
+    }
+
+    @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);
+            }
+        } else {
+            handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(null,
+                    0), null);
+        }
+    }
+
+    @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);
+            }
+        } else {
+            handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null, 0),
+                    null);
+        }
+    }
+
+    @Override
+    public void onAuthenticatorIdRetrieved(long authenticatorId) {
+        handleResponse(FingerprintGetAuthenticatorIdClient.class,
+                (c) -> c.onAuthenticatorIdRetrieved(authenticatorId), null);
+    }
+
+    @Override
+    public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
+        handleResponse(FingerprintInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated(
+                newAuthenticatorId), null);
+    }
+
+    private <T> void handleResponse(@NonNull Class<T> className,
+            @NonNull Consumer<T> action,
+            @Nullable Consumer<BaseClientMonitor> alternateAction) {
+        mScheduler.getHandler().post(() -> {
+            final BaseClientMonitor client = mScheduler.getCurrentClient();
+            if (className.isInstance(client)) {
+                action.accept((T) client);
+            } else {
+                Slog.e(TAG, "Client monitor is not an instance of " + className.getName());
+                if (alternateAction != null) {
+                    alternateAction.accept(client);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onSessionClosed() {
+        mScheduler.getHandler().post(mScheduler::onUserStopped);
+    }
+}
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 55861bb..299a310 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
@@ -16,10 +16,13 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
-import static com.android.server.biometrics.sensors.fingerprint.aidl.Sensor.HalSessionCallback;
-
 import android.annotation.NonNull;
 import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+
+import com.android.server.biometrics.sensors.fingerprint.hidl.AidlToHidlAdapter;
+
+import java.util.function.Supplier;
 
 /**
  * A holder for an AIDL {@link ISession} with additional metadata about the current user
@@ -30,14 +33,22 @@
     private final int mHalInterfaceVersion;
     @NonNull private final ISession mSession;
     private final int mUserId;
-    @NonNull private final HalSessionCallback mHalSessionCallback;
+    @NonNull private final AidlResponseHandler mAidlResponseHandler;
 
     public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId,
-            HalSessionCallback halSessionCallback) {
+            AidlResponseHandler aidlResponseHandler) {
         mHalInterfaceVersion = halInterfaceVersion;
         mSession = session;
         mUserId = userId;
-        mHalSessionCallback = halSessionCallback;
+        mAidlResponseHandler = aidlResponseHandler;
+    }
+
+    public AidlSession(@NonNull Supplier<IBiometricsFingerprint> session,
+            int userId, AidlResponseHandler aidlResponseHandler) {
+        mSession = new AidlToHidlAdapter(session, userId, aidlResponseHandler);
+        mHalInterfaceVersion = 0;
+        mUserId = userId;
+        mAidlResponseHandler = aidlResponseHandler;
     }
 
     /** The underlying {@link ISession}. */
@@ -51,8 +62,8 @@
     }
 
     /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
-    HalSessionCallback getHalSessionCallback() {
-        return mHalSessionCallback;
+    AidlResponseHandler getHalSessionCallback() {
+        return mAidlResponseHandler;
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 54d1faa..337c3c2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -21,6 +21,7 @@
 import android.app.TaskStackListener;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
 import android.hardware.biometrics.BiometricManager.Authenticators;
@@ -51,8 +52,8 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.SensorOverlays;
 import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
@@ -66,7 +67,7 @@
  * Fingerprint-specific authentication client supporting the {@link
  * android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
  */
-class FingerprintAuthenticationClient
+public class FingerprintAuthenticationClient
         extends AuthenticationClient<AidlSession, FingerprintAuthenticateOptions>
         implements Udfps, LockoutConsumer, PowerPressHandler {
     private static final String TAG = "FingerprintAuthenticationClient";
@@ -93,7 +94,7 @@
     private Runnable mAuthSuccessRunnable;
     private final Clock mClock;
 
-    FingerprintAuthenticationClient(
+    public FingerprintAuthenticationClient(
             @NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token,
@@ -108,14 +109,14 @@
             @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric,
             @Nullable TaskStackListener taskStackListener,
-            @NonNull LockoutCache lockoutCache,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull Handler handler,
             @Authenticators.Types int biometricStrength,
-            @NonNull Clock clock) {
+            @NonNull Clock clock,
+            @Nullable LockoutTracker lockoutTracker) {
         super(
                 context,
                 lazyDaemon,
@@ -130,7 +131,7 @@
                 biometricContext,
                 isStrongBiometric,
                 taskStackListener,
-                null /* lockoutCache */,
+                lockoutTracker,
                 allowBackgroundAuthentication,
                 false /* shouldVibrate */,
                 biometricStrength);
@@ -211,6 +212,7 @@
             boolean authenticated,
             ArrayList<Byte> token) {
         super.onAuthenticated(identifier, authenticated, token);
+        handleLockout(authenticated);
         if (authenticated) {
             mState = STATE_STOPPED;
             mSensorOverlays.hide(getSensorId());
@@ -219,6 +221,32 @@
         }
     }
 
+    private void handleLockout(boolean authenticated) {
+        if (getLockoutTracker() == null) {
+            Slog.d(TAG, "Lockout is implemented by the HAL");
+            return;
+        }
+        if (authenticated) {
+            getLockoutTracker().resetFailedAttemptsForUser(true /* clearAttemptCounter */,
+                    getTargetUserId());
+        } else {
+            @LockoutTracker.LockoutMode final int lockoutMode =
+                    getLockoutTracker().getLockoutModeForUser(getTargetUserId());
+            if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
+                Slog.w(TAG, "Fingerprint locked out, lockoutMode(" + lockoutMode + ")");
+                final int errorCode = lockoutMode == LockoutTracker.LOCKOUT_TIMED
+                        ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
+                        : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
+                // Send the error, but do not invoke the FinishCallback yet. Since lockout is not
+                // controlled by the HAL, the framework must stop the sensor before finishing the
+                // client.
+                mSensorOverlays.hide(getSensorId());
+                onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
+                cancel();
+            }
+        }
+    }
+
     @Override
     public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) {
         // For UDFPS, notify SysUI with acquiredInfo, so that the illumination can be turned off
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 4502e5d..e2413ee 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -43,7 +43,8 @@
  * Performs fingerprint detection without exposing any matching information (e.g. accept/reject
  * have the same haptic, lockout counter is not increased).
  */
-class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements DetectionConsumer {
+public class FingerprintDetectClient extends AcquisitionClient<AidlSession>
+        implements DetectionConsumer {
 
     private static final String TAG = "FingerprintDetectClient";
 
@@ -52,7 +53,8 @@
     @NonNull private final SensorOverlays mSensorOverlays;
     @Nullable private ICancellationSignal mCancellationSignal;
 
-    FingerprintDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+    public FingerprintDetectClient(@NonNull Context context,
+            @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener,
             @NonNull FingerprintAuthenticateOptions options,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 46ff6b4..06550d8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -49,14 +49,13 @@
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.EnrollClient;
 import com.android.server.biometrics.sensors.SensorOverlays;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
 import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
 
 import java.util.function.Supplier;
 
-class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps,
+public class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps,
         PowerPressHandler {
 
     private static final String TAG = "FingerprintEnrollClient";
@@ -72,12 +71,16 @@
 
     private static boolean shouldVibrateFor(Context context,
             FingerprintSensorPropertiesInternal sensorProps) {
-        final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
-        final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled();
-        return !sensorProps.isAnyUdfpsType() || isAccessbilityEnabled;
+        if (sensorProps != null) {
+            final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
+            final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled();
+            return !sensorProps.isAnyUdfpsType() || isAccessbilityEnabled;
+        } else {
+            return true;
+        }
     }
 
-    FingerprintEnrollClient(@NonNull Context context,
+    public FingerprintEnrollClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
@@ -89,8 +92,8 @@
             int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
         // UDFPS haptics occur when an image is acquired (instead of when the result is known)
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
-                0 /* timeoutSec */, sensorId, shouldVibrateFor(context, sensorProps), logger,
-                biometricContext);
+                0 /* timeoutSec */, sensorId, shouldVibrateFor(context, sensorProps),
+                logger, biometricContext);
         setRequestId(requestId);
         mSensorProps = sensorProps;
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
@@ -136,7 +139,7 @@
                 acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD;
         // For UDFPS, notify SysUI that the illumination can be turned off.
         // See AcquiredInfo#GOOD and AcquiredInfo#RETRYING_CAPTURE
-        if (mSensorProps.isAnyUdfpsType()) {
+        if (mSensorProps != null && mSensorProps.isAnyUdfpsType()) {
             if (acquiredGood && mShouldVibrate) {
                 vibrateSuccess();
             }
@@ -162,8 +165,7 @@
 
     @Override
     protected boolean hasReachedEnrollmentLimit() {
-        return FingerprintUtils.getInstance(getSensorId())
-                .getBiometricsForUser(getContext(), getTargetUserId()).size()
+        return mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).size()
                 >= mMaxTemplatesPerUser;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
index ddae8be..ce693ff 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
@@ -33,10 +33,10 @@
 /**
  * Fingerprint-specific generateChallenge client for the {@link IFingerprint} AIDL HAL interface.
  */
-class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSession> {
+public class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSession> {
     private static final String TAG = "FingerprintGenerateChallengeClient";
 
-    FingerprintGenerateChallengeClient(@NonNull Context context,
+    public FingerprintGenerateChallengeClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index ff9127f..5edc2ca 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -39,9 +39,10 @@
  * Fingerprint-specific internal cleanup client supporting the
  * {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
  */
-class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint, AidlSession> {
+public class FingerprintInternalCleanupClient
+        extends InternalCleanupClient<Fingerprint, AidlSession> {
 
-    FingerprintInternalCleanupClient(@NonNull Context context,
+    public FingerprintInternalCleanupClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon,
             int userId, @NonNull String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
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 e42b664..9985b06 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
@@ -428,8 +428,8 @@
             final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
                     mFingerprintSensors.get(sensorId).getLazySession(), token, id,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
-                    opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+                    opPackageName, FingerprintUtils.getInstance(sensorId),
+                    sensorId, createLogger(BiometricsProtoEnums.ACTION_ENROLL,
                             BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext,
                     mFingerprintSensors.get(sensorId).getSensorProperties(),
@@ -496,12 +496,13 @@
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
                             mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric,
-                    mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(),
+                    mTaskStackListener,
                     mUdfpsOverlayController, mSidefpsController,
                     allowBackgroundAuthentication,
                     mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
                     Utils.getCurrentStrength(sensorId),
-                    SystemClock.elapsedRealtimeClock());
+                    SystemClock.elapsedRealtimeClock(),
+                    null /* lockoutTracker */);
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
 
                 @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
index d559bb1..4f08f6f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
@@ -37,12 +37,12 @@
  * Fingerprint-specific removal client supporting the
  * {@link android.hardware.biometrics.fingerprint.IFingerprint} interface.
  */
-class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> {
+public class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> {
     private static final String TAG = "FingerprintRemovalClient";
 
     private final int[] mBiometricIds;
 
-    FingerprintRemovalClient(@NonNull Context context,
+    public FingerprintRemovalClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
             @Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId,
             @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
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 7a62034..ec225a6 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
@@ -32,7 +32,6 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.HalClientMonitor;
-import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 
@@ -43,19 +42,20 @@
  * Updates the framework's lockout cache and notifies clients such as Keyguard when lockout is
  * cleared.
  */
-class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implements ErrorConsumer {
+public class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession>
+        implements ErrorConsumer {
 
     private static final String TAG = "FingerprintResetLockoutClient";
 
     private final HardwareAuthToken mHardwareAuthToken;
-    private final LockoutCache mLockoutCache;
+    private final LockoutTracker mLockoutCache;
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     private final int mBiometricStrength;
 
-    FingerprintResetLockoutClient(@NonNull Context context,
+    public FingerprintResetLockoutClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
-            @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
+            @NonNull byte[] hardwareAuthToken, @NonNull LockoutTracker lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @Authenticators.Types int biometricStrength) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
@@ -107,10 +107,11 @@
      * be used instead.
      */
     static void resetLocalLockoutStateToNone(int sensorId, int userId,
-            @NonNull LockoutCache lockoutTracker,
+            @NonNull LockoutTracker lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull AuthSessionCoordinator authSessionCoordinator,
             @Authenticators.Types int biometricStrength, long requestId) {
+        lockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */, userId);
         lockoutTracker.setLockoutModeForUser(userId, LockoutTracker.LOCKOUT_NONE);
         lockoutResetDispatcher.notifyLockoutResetCallbacks(sensorId);
         authSessionCoordinator.resetLockoutFor(userId, biometricStrength, requestId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
index afa62e2..23d8e1e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
@@ -32,13 +32,13 @@
 /**
  * Fingerprint-specific revokeChallenge client for the {@link IFingerprint} AIDL HAL interface.
  */
-class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession> {
+public class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession> {
 
-    private static final String TAG = "FingerpirntRevokeChallengeClient";
+    private static final String TAG = "FingerprintRevokeChallengeClient";
 
     private final long mChallenge;
 
-    FingerprintRevokeChallengeClient(@NonNull Context context,
+    public FingerprintRevokeChallengeClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
             int userId, @NonNull String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@@ -57,7 +57,7 @@
         }
     }
 
-    void onChallengeRevoked(int sensorId, int userId, long challenge) {
+    void onChallengeRevoked(long challenge) {
         final boolean success = challenge == mChallenge;
         mCallback.onClientFinished(FingerprintRevokeChallengeClient.this, success);
     }
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 56b85ce..893cb8f 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
@@ -23,13 +23,9 @@
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.biometrics.fingerprint.Error;
 import android.hardware.biometrics.fingerprint.ISession;
-import android.hardware.biometrics.fingerprint.ISessionCallback;
-import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.keymaster.HardwareAuthToken;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -39,34 +35,25 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.AcquisitionClient;
-import com.android.server.biometrics.sensors.AuthSessionCoordinator;
-import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
-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.RemovalConsumer;
 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.HashMap;
 import java.util.Map;
 import java.util.function.Supplier;
@@ -93,348 +80,6 @@
     @Nullable AidlSession mCurrentSession;
     @NonNull private final Supplier<AidlSession> mLazySession;
 
-    @VisibleForTesting
-    public static class HalSessionCallback extends ISessionCallback.Stub {
-
-        /**
-         * Interface to sends results to the HalSessionCallback's owner.
-         */
-        public interface Callback {
-            /**
-             * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
-             */
-            void onHardwareUnavailable();
-        }
-
-        @NonNull
-        private final Context mContext;
-        @NonNull
-        private final Handler mHandler;
-        @NonNull
-        private final String mTag;
-        @NonNull
-        private final UserAwareBiometricScheduler mScheduler;
-        private final int mSensorId;
-        private final int mUserId;
-        @NonNull
-        private final LockoutCache mLockoutCache;
-        @NonNull
-        private final LockoutResetDispatcher mLockoutResetDispatcher;
-        @NonNull
-        private AuthSessionCoordinator mAuthSessionCoordinator;
-        @NonNull
-        private final Callback mCallback;
-
-        HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
-                @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
-                @NonNull LockoutCache lockoutTracker,
-                @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-                @NonNull AuthSessionCoordinator authSessionCoordinator,
-                @NonNull Callback callback) {
-            mContext = context;
-            mHandler = handler;
-            mTag = tag;
-            mScheduler = scheduler;
-            mSensorId = sensorId;
-            mUserId = userId;
-            mLockoutCache = lockoutTracker;
-            mLockoutResetDispatcher = lockoutResetDispatcher;
-            mAuthSessionCoordinator = authSessionCoordinator;
-            mCallback = callback;
-        }
-
-        @Override
-        public int getInterfaceVersion() {
-            return this.VERSION;
-        }
-
-        @Override
-        public String getInterfaceHash() {
-            return this.HASH;
-        }
-
-        @Override
-        public void onChallengeGenerated(long challenge) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FingerprintGenerateChallengeClient)) {
-                    Slog.e(mTag, "onChallengeGenerated for wrong client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FingerprintGenerateChallengeClient generateChallengeClient =
-                        (FingerprintGenerateChallengeClient) client;
-                generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge);
-            });
-        }
-
-        @Override
-        public void onChallengeRevoked(long challenge) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FingerprintRevokeChallengeClient)) {
-                    Slog.e(mTag, "onChallengeRevoked for wrong client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FingerprintRevokeChallengeClient revokeChallengeClient =
-                        (FingerprintRevokeChallengeClient) client;
-                revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge);
-            });
-        }
-
-        @Override
-        public void onAcquired(byte info, int vendorCode) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof AcquisitionClient)) {
-                    Slog.e(mTag, "onAcquired for non-acquisition client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
-                acquisitionClient.onAcquired(AidlConversionUtils.toFrameworkAcquiredInfo(info),
-                        vendorCode);
-            });
-        }
-
-        @Override
-        public void onError(byte error, int vendorCode) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                Slog.d(mTag, "onError"
-                        + ", client: " + Utils.getClientName(client)
-                        + ", error: " + error
-                        + ", vendorCode: " + vendorCode);
-                if (!(client instanceof ErrorConsumer)) {
-                    Slog.e(mTag, "onError for non-error consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final ErrorConsumer errorConsumer = (ErrorConsumer) client;
-                errorConsumer.onError(AidlConversionUtils.toFrameworkError(error), vendorCode);
-
-                if (error == Error.HW_UNAVAILABLE) {
-                    mCallback.onHardwareUnavailable();
-                }
-            });
-        }
-
-        @Override
-        public void onEnrollmentProgress(int enrollmentId, int remaining) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FingerprintEnrollClient)) {
-                    Slog.e(mTag, "onEnrollmentProgress for non-enroll client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final int currentUserId = client.getTargetUserId();
-                final CharSequence name = FingerprintUtils.getInstance(mSensorId)
-                        .getUniqueName(mContext, currentUserId);
-                final Fingerprint fingerprint = new Fingerprint(name, enrollmentId, mSensorId);
-
-                final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client;
-                enrollClient.onEnrollResult(fingerprint, remaining);
-            });
-        }
-
-        @Override
-        public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof AuthenticationConsumer)) {
-                    Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final AuthenticationConsumer authenticationConsumer =
-                        (AuthenticationConsumer) client;
-                final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
-                final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
-                final ArrayList<Byte> byteList = new ArrayList<>();
-                for (byte b : byteArray) {
-                    byteList.add(b);
-                }
-
-                authenticationConsumer.onAuthenticated(fp, true /* authenticated */, byteList);
-            });
-        }
-
-        @Override
-        public void onAuthenticationFailed() {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof AuthenticationConsumer)) {
-                    Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final AuthenticationConsumer authenticationConsumer =
-                        (AuthenticationConsumer) client;
-                final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId);
-                authenticationConsumer
-                        .onAuthenticated(fp, false /* authenticated */, null /* hat */);
-            });
-        }
-
-        @Override
-        public void onLockoutTimed(long durationMillis) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof LockoutConsumer)) {
-                    Slog.e(mTag, "onLockoutTimed for non-lockout consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
-                lockoutConsumer.onLockoutTimed(durationMillis);
-            });
-        }
-
-        @Override
-        public void onLockoutPermanent() {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof LockoutConsumer)) {
-                    Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
-                lockoutConsumer.onLockoutPermanent();
-            });
-        }
-
-        @Override
-        public void onLockoutCleared() {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FingerprintResetLockoutClient)) {
-                    Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL");
-                    // Given that onLockoutCleared() can happen at any time, and is not necessarily
-                    // coming from a specific client, set this to -1 to indicate it wasn't for a
-                    // specific request.
-                    FingerprintResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
-                            mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
-                            Utils.getCurrentStrength(mSensorId), -1 /* requestId */);
-                } else {
-                    Slog.d(mTag, "onLockoutCleared after resetLockout");
-                    final FingerprintResetLockoutClient resetLockoutClient =
-                            (FingerprintResetLockoutClient) client;
-                    resetLockoutClient.onLockoutCleared();
-                }
-            });
-        }
-
-        @Override
-        public void onInteractionDetected() {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FingerprintDetectClient)) {
-                    Slog.e(mTag, "onInteractionDetected for non-detect client: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FingerprintDetectClient fingerprintDetectClient =
-                        (FingerprintDetectClient) client;
-                fingerprintDetectClient.onInteractionDetected();
-            });
-        }
-
-        @Override
-        public void onEnrollmentsEnumerated(int[] enrollmentIds) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof EnumerateConsumer)) {
-                    Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final EnumerateConsumer enumerateConsumer =
-                        (EnumerateConsumer) client;
-                if (enrollmentIds.length > 0) {
-                    for (int i = 0; i < enrollmentIds.length; i++) {
-                        final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
-                        enumerateConsumer.onEnumerationResult(fp, enrollmentIds.length - i - 1);
-                    }
-                } else {
-                    enumerateConsumer.onEnumerationResult(null /* identifier */, 0);
-                }
-            });
-        }
-
-        @Override
-        public void onEnrollmentsRemoved(int[] enrollmentIds) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof RemovalConsumer)) {
-                    Slog.e(mTag, "onRemoved for non-removal consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final RemovalConsumer removalConsumer = (RemovalConsumer) client;
-                if (enrollmentIds.length > 0) {
-                    for (int i  = 0; i < enrollmentIds.length; i++) {
-                        final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
-                        removalConsumer.onRemoved(fp, enrollmentIds.length - i - 1);
-                    }
-                } else {
-                    removalConsumer.onRemoved(null, 0);
-                }
-            });
-        }
-
-        @Override
-        public void onAuthenticatorIdRetrieved(long authenticatorId) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FingerprintGetAuthenticatorIdClient)) {
-                    Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FingerprintGetAuthenticatorIdClient getAuthenticatorIdClient =
-                        (FingerprintGetAuthenticatorIdClient) client;
-                getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId);
-            });
-        }
-
-        @Override
-        public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
-            mHandler.post(() -> {
-                final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof FingerprintInvalidationClient)) {
-                    Slog.e(mTag, "onAuthenticatorIdInvalidated for wrong consumer: "
-                            + Utils.getClientName(client));
-                    return;
-                }
-
-                final FingerprintInvalidationClient invalidationClient =
-                        (FingerprintInvalidationClient) client;
-                invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId);
-            });
-        }
-
-        @Override
-        public void onSessionClosed() {
-            mHandler.post(mScheduler::onUserStopped);
-        }
-    }
-
     Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
@@ -466,9 +111,9 @@
                     public StartUserClient<?, ?> getStartUserClient(int newUserId) {
                         final int sensorId = mSensorProperties.sensorId;
 
-                        final HalSessionCallback resultController = new HalSessionCallback(mContext,
-                                mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache,
-                                lockoutResetDispatcher,
+                        final AidlResponseHandler resultController = new AidlResponseHandler(
+                                mContext, mScheduler, sensorId, newUserId,
+                                mLockoutCache, lockoutResetDispatcher,
                                 biometricContext.getAuthSessionCoordinator(), () -> {
                             Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
                             mCurrentSession = null;
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/AidlToHidlAdapter.java
new file mode 100644
index 0000000..b48d232
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java
@@ -0,0 +1,231 @@
+/*
+ * 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.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.os.IBinder;
+import android.os.RemoteException;
+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 java.util.function.Supplier;
+
+/**
+ * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation.
+ */
+public class AidlToHidlAdapter implements ISession {
+    private final String TAG = "AidlToHidlAdapter";
+    @VisibleForTesting
+    static final int ENROLL_TIMEOUT_SEC = 60;
+    @NonNull
+    private final Supplier<IBiometricsFingerprint> mSession;
+    private final int mUserId;
+    private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter;
+
+    public AidlToHidlAdapter(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;
+    }
+
+    @Override
+    public void generateChallenge() throws RemoteException {
+        long challenge = mSession.get().preEnroll();
+        mHidlToAidlCallbackConverter.onChallengeGenerated(challenge);
+    }
+
+    @Override
+    public void revokeChallenge(long challenge) throws RemoteException {
+        mSession.get().postEnroll();
+        mHidlToAidlCallbackConverter.onChallengeRevoked(0L);
+    }
+
+    @Override
+    public ICancellationSignal enroll(HardwareAuthToken hat) throws RemoteException {
+        mSession.get().enroll(HardwareAuthTokenUtils.toByteArray(hat), mUserId,
+                ENROLL_TIMEOUT_SEC);
+        return new Cancellation();
+    }
+
+    @Override
+    public ICancellationSignal authenticate(long operationId) throws RemoteException {
+        mSession.get().authenticate(operationId, mUserId);
+        return new Cancellation();
+    }
+
+    @Override
+    public ICancellationSignal detectInteraction() throws RemoteException {
+        mSession.get().authenticate(0, mUserId);
+        return new Cancellation();
+    }
+
+    @Override
+    public void enumerateEnrollments() throws RemoteException {
+        mSession.get().enumerate();
+    }
+
+    @Override
+    public void removeEnrollments(int[] enrollmentIds) throws RemoteException {
+        if (enrollmentIds.length > 1) {
+            mSession.get().remove(mUserId, 0);
+        } else {
+            mSession.get().remove(mUserId, enrollmentIds[0]);
+        }
+    }
+
+    @Override
+    public void onPointerDown(int pointerId, int x, int y, float minor, float major)
+            throws RemoteException {
+        UdfpsHelper.onFingerDown(mSession.get(), x, y, minor, major);
+    }
+
+    @Override
+    public void onPointerUp(int pointerId) throws RemoteException {
+        UdfpsHelper.onFingerUp(mSession.get());
+    }
+
+    @Override
+    public void getAuthenticatorId() throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public void invalidateAuthenticatorId() throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public void resetLockout(HardwareAuthToken hat) throws RemoteException {
+        mHidlToAidlCallbackConverter.onResetLockout();
+    }
+
+    @Override
+    public void close() throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public void onUiReady() throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public ICancellationSignal authenticateWithContext(long operationId, OperationContext context)
+            throws RemoteException {
+        //Unsupported in HIDL
+        return null;
+    }
+
+    @Override
+    public ICancellationSignal enrollWithContext(HardwareAuthToken hat, OperationContext context)
+            throws RemoteException {
+        //Unsupported in HIDL
+        return null;
+    }
+
+    @Override
+    public ICancellationSignal detectInteractionWithContext(OperationContext context)
+            throws RemoteException {
+        //Unsupported in HIDL
+        return null;
+    }
+
+    @Override
+    public void onPointerDownWithContext(PointerContext context) throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public void onPointerUpWithContext(PointerContext context) throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public void onContextChanged(OperationContext context) throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public void onPointerCancelWithContext(PointerContext context) throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public void setIgnoreDisplayTouches(boolean shouldIgnore) throws RemoteException {
+        //Unsupported in HIDL
+    }
+
+    @Override
+    public int getInterfaceVersion() throws RemoteException {
+        //Unsupported in HIDL
+        return 0;
+    }
+
+    @Override
+    public String getInterfaceHash() throws RemoteException {
+        //Unsupported in HIDL
+        return null;
+    }
+
+    private class Cancellation extends ICancellationSignal.Stub {
+
+        Cancellation() {}
+        @Override
+        public void cancel() throws RemoteException {
+            try {
+                mSession.get().cancel();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception when requesting cancel", e);
+            }
+        }
+
+        @Override
+        public int getInterfaceVersion() throws RemoteException {
+            return 0;
+        }
+
+        @Override
+        public String getInterfaceHash() throws RemoteException {
+            return null;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index a655f360..8bfa560 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -54,6 +54,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
 import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
@@ -64,6 +65,7 @@
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
@@ -74,6 +76,7 @@
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 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.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.PerformanceTracker;
@@ -82,6 +85,8 @@
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -132,6 +137,7 @@
     private final boolean mIsUdfps;
     private final int mSensorId;
     private final boolean mIsPowerbuttonFps;
+    private AidlSession mSession;
 
     private final class BiometricTaskStackListener extends TaskStackListener {
         @Override
@@ -413,6 +419,20 @@
         });
     }
 
+    synchronized AidlSession getSession() {
+        if (mDaemon != null && mSession != null) {
+            return mSession;
+        } else {
+            return mSession = new AidlSession(this::getDaemon,
+                    mCurrentUserId, new AidlResponseHandler(mContext,
+                    mScheduler, mSensorId, mCurrentUserId, new LockoutCache(),
+                    mLockoutResetDispatcher, new AuthSessionCoordinator(), () -> {
+                        mDaemon = null;
+                        mCurrentUserId = UserHandle.USER_NULL;
+                    }));
+        }
+    }
+
     @VisibleForTesting
     synchronized IBiometricsFingerprint getDaemon() {
         if (mTestHalEnabled) {
@@ -518,6 +538,10 @@
             public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
                     boolean success) {
                 if (success) {
+                    if (mCurrentUserId != targetUserId) {
+                        // Create new session with updated user ID
+                        mSession = null;
+                    }
                     mCurrentUserId = targetUserId;
                 } else {
                     Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId);
@@ -554,47 +578,116 @@
         // Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler
         // thread.
         mHandler.post(() -> {
-            final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
-                    userId, mContext.getOpPackageName(), sensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN,
-                            mAuthenticationStatsCollector),
-                    mBiometricContext, mLockoutTracker);
-            mScheduler.scheduleClientMonitor(client);
+            if (Flags.deHidl()) {
+                scheduleResetLockoutAidl(sensorId, userId, hardwareAuthToken);
+            } else {
+                scheduleResetLockoutHidl(sensorId, userId);
+            }
         });
     }
 
+    private void scheduleResetLockoutAidl(int sensorId, int userId,
+            @Nullable byte[] hardwareAuthToken) {
+        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient client =
+                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient(
+                        mContext, this::getSession, userId, mContext.getOpPackageName(),
+                        sensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector),
+                        mBiometricContext, hardwareAuthToken, mLockoutTracker,
+                        mLockoutResetDispatcher,
+                        Utils.getCurrentStrength(sensorId));
+        mScheduler.scheduleClientMonitor(client);
+    }
+
+    private void scheduleResetLockoutHidl(int sensorId, int userId) {
+        final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
+                userId, mContext.getOpPackageName(), sensorId,
+                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN,
+                        mAuthenticationStatsCollector),
+                mBiometricContext, mLockoutTracker);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
     @Override
     public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
         mHandler.post(() -> {
-            final FingerprintGenerateChallengeClient client =
-                    new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token,
-                            new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
-                            mSensorProperties.sensorId,
-                            createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
-                                    mAuthenticationStatsCollector),
-                            mBiometricContext);
-            mScheduler.scheduleClientMonitor(client);
+            if (Flags.deHidl()) {
+                scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName);
+            } else {
+                scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName);
+            }
         });
     }
 
+    private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token,
+            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
+        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient client =
+                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient(
+                        mContext, this::getSession, token,
+                        new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+                        mSensorProperties.sensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector),
+                        mBiometricContext);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
+    private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token,
+            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
+        final FingerprintGenerateChallengeClient client =
+                new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token,
+                        new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+                        mSensorProperties.sensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector),
+                        mBiometricContext);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
     @Override
     public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull String opPackageName, long challenge) {
         mHandler.post(() -> {
-            final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(
-                    mContext, mLazyDaemon, token, userId, opPackageName,
-                    mSensorProperties.sensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN,
-                            mAuthenticationStatsCollector),
-                    mBiometricContext);
-            mScheduler.scheduleClientMonitor(client);
+            if (Flags.deHidl()) {
+                scheduleRevokeChallengeAidl(userId, token, opPackageName);
+            } else {
+                scheduleRevokeChallengeHidl(userId, token, opPackageName);
+            }
         });
     }
 
+    private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token,
+            @NonNull String opPackageName) {
+        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient client =
+                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient(
+                        mContext, this::getSession,
+                        token, userId, opPackageName,
+                        mSensorProperties.sensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector),
+                        mBiometricContext, 0L);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
+    private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token,
+            @NonNull String opPackageName) {
+        final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(
+                mContext, mLazyDaemon, token, userId, opPackageName,
+                mSensorProperties.sensorId,
+                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN,
+                        mAuthenticationStatsCollector),
+                mBiometricContext);
+        mScheduler.scheduleClientMonitor(client);
+    }
+
     @Override
     public long scheduleEnroll(int sensorId, @NonNull IBinder token,
             @NonNull byte[] hardwareAuthToken, int userId,
@@ -604,40 +697,98 @@
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
-            final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
-                    mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
-                    userId, hardwareAuthToken, opPackageName,
-                    FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
-                    mSensorProperties.sensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_ENROLL,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason);
-            mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
-                @Override
-                public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-                    mBiometricStateCallback.onClientStarted(clientMonitor);
-                }
-
-                @Override
-                public void onBiometricAction(int action) {
-                    mBiometricStateCallback.onBiometricAction(action);
-                }
-
-                @Override
-                public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                        boolean success) {
-                    mBiometricStateCallback.onClientFinished(clientMonitor, success);
-                    if (success) {
-                        // Update authenticatorIds
-                        scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
-                                true /* force */);
-                    }
-                }
-            });
+            if (Flags.deHidl()) {
+                scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver,
+                        opPackageName, enrollReason, id);
+            } else {
+                scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver,
+                        opPackageName, enrollReason, id);
+            }
         });
         return id;
     }
 
+    private void scheduleEnrollHidl(@NonNull IBinder token,
+            @NonNull byte[] hardwareAuthToken, int userId,
+            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
+            @FingerprintManager.EnrollReason int enrollReason, long id) {
+        final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
+                mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
+                userId, hardwareAuthToken, opPackageName,
+                FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
+                mSensorProperties.sensorId,
+                createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason);
+        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+            @Override
+            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                mBiometricStateCallback.onClientStarted(clientMonitor);
+            }
+
+            @Override
+            public void onBiometricAction(int action) {
+                mBiometricStateCallback.onBiometricAction(action);
+            }
+
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                mBiometricStateCallback.onClientFinished(clientMonitor, success);
+                if (success) {
+                    // Update authenticatorIds
+                    scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
+                            true /* force */);
+                }
+            }
+        });
+    }
+
+    private void scheduleEnrollAidl(@NonNull IBinder token,
+            @NonNull byte[] hardwareAuthToken, int userId,
+            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
+            @FingerprintManager.EnrollReason int enrollReason, long id) {
+        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient
+                client =
+                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient(
+                        mContext,
+                        this::getSession, token, id,
+                        new ClientMonitorCallbackConverter(receiver),
+                        userId, hardwareAuthToken, opPackageName,
+                        FingerprintUtils.getLegacyInstance(mSensorId),
+                        mSensorProperties.sensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector),
+                        mBiometricContext, null /* sensorProps */,
+                        mUdfpsOverlayController, mSidefpsController,
+                        mContext.getResources().getInteger(
+                                com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser),
+                        enrollReason);
+        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+            @Override
+            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                mBiometricStateCallback.onClientStarted(clientMonitor);
+            }
+
+            @Override
+            public void onBiometricAction(int action) {
+                mBiometricStateCallback.onBiometricAction(action);
+            }
+
+            @Override
+            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                    boolean success) {
+                mBiometricStateCallback.onClientFinished(clientMonitor, success);
+                if (success) {
+                    // Update authenticatorIds
+                    scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
+                            true /* force */);
+                }
+            }
+        });
+    }
+
     @Override
     public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
         mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
@@ -653,17 +804,46 @@
             scheduleUpdateActiveUserWithoutHandler(options.getUserId());
 
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
-            final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
-                    mLazyDaemon, token, id, listener, options,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
-                            mAuthenticationStatsCollector),
-                    mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
-            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+
+            if (Flags.deHidl()) {
+                scheduleFingerDetectAidl(token, listener, options, statsClient, id,
+                        isStrongBiometric);
+            } else {
+                scheduleFingerDetectHidl(token, listener, options, statsClient, id,
+                        isStrongBiometric);
+            }
         });
 
         return id;
     }
 
+    private void scheduleFingerDetectHidl(@NonNull IBinder token,
+            @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FingerprintAuthenticateOptions options,
+            int statsClient, long id, boolean isStrongBiometric) {
+        final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
+                mLazyDaemon, token, id, listener, options,
+                createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                        mAuthenticationStatsCollector),
+                mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
+        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+    }
+
+    private void scheduleFingerDetectAidl(@NonNull IBinder token,
+            @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FingerprintAuthenticateOptions options,
+            int statsClient, long id, boolean isStrongBiometric) {
+        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient
+                client =
+                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient(
+                        mContext,
+                        this::getSession, token, id, listener, options,
+                        createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                                mAuthenticationStatsCollector),
+                        mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
+        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+    }
+
     @Override
     public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter listener,
@@ -674,20 +854,55 @@
             scheduleUpdateActiveUserWithoutHandler(options.getUserId());
 
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
-            final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
-                    mContext, mLazyDaemon, token, requestId, listener, operationId,
-                    restricted, options, cookie, false /* requireConfirmation */,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
-                            mAuthenticationStatsCollector),
-                    mBiometricContext, isStrongBiometric,
-                    mTaskStackListener, mLockoutTracker,
-                    mUdfpsOverlayController, mSidefpsController,
-                    allowBackgroundAuthentication, mSensorProperties,
-                    Utils.getCurrentStrength(mSensorId));
-            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+
+            if (Flags.deHidl()) {
+                scheduleAuthenticateAidl(token, operationId, cookie, listener, options, requestId,
+                        restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
+            } else {
+                scheduleAuthenticateHidl(token, operationId, cookie, listener, options, requestId,
+                        restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
+            }
         });
     }
 
+    private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId,
+            int cookie, @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FingerprintAuthenticateOptions options,
+            long requestId, boolean restricted, int statsClient,
+            boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
+        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient
+                client =
+                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient(
+                        mContext, this::getSession, token, requestId, listener, operationId,
+                        restricted, options, cookie, false /* requireConfirmation */,
+                        createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                                mAuthenticationStatsCollector),
+                        mBiometricContext, isStrongBiometric,
+                        mTaskStackListener,
+                        mUdfpsOverlayController, mSidefpsController,
+                        allowBackgroundAuthentication, mSensorProperties, mHandler,
+                        Utils.getCurrentStrength(mSensorId), null /* clock */, mLockoutTracker);
+        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+    }
+
+    private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId,
+            int cookie, @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FingerprintAuthenticateOptions options,
+            long requestId, boolean restricted, int statsClient,
+            boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
+        final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
+                mContext, mLazyDaemon, token, requestId, listener, operationId,
+                restricted, options, cookie, false /* requireConfirmation */,
+                createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                        mAuthenticationStatsCollector),
+                mBiometricContext, isStrongBiometric,
+                mTaskStackListener, mLockoutTracker,
+                mUdfpsOverlayController, mSidefpsController,
+                allowBackgroundAuthentication, mSensorProperties,
+                Utils.getCurrentStrength(mSensorId));
+        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+    }
+
     @Override
     public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter listener,
@@ -719,17 +934,41 @@
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
-            final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
-                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
-                    userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
-                    mSensorProperties.sensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext, mAuthenticatorIds);
-            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+            if (Flags.deHidl()) {
+                scheduleRemoveAidl(token, receiver, fingerId, userId, opPackageName);
+            } else {
+                scheduleRemoveHidl(token, receiver, fingerId, userId, opPackageName);
+            }
         });
     }
 
+    private void scheduleRemoveHidl(@NonNull IBinder token,
+            @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
+            @NonNull String opPackageName) {
+        final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
+                mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
+                userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
+                mSensorProperties.sensorId,
+                createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                mBiometricContext, mAuthenticatorIds);
+        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+    }
+
+    private void scheduleRemoveAidl(@NonNull IBinder token,
+            @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
+            @NonNull String opPackageName) {
+        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient client =
+                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient(
+                        mContext, this::getSession, token,
+                        new ClientMonitorCallbackConverter(receiver), new int[]{fingerId}, userId,
+                        opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), mSensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                        mBiometricContext, mAuthenticatorIds);
+        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+    }
+
     @Override
     public void scheduleRemoveAll(int sensorId, @NonNull IBinder token,
             @NonNull IFingerprintServiceReceiver receiver, int userId,
@@ -738,15 +977,11 @@
             scheduleUpdateActiveUserWithoutHandler(userId);
 
             // For IBiometricsFingerprint@2.1, remove(0) means remove all enrollments
-            final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
-                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver),
-                    0 /* fingerprintId */, userId, opPackageName,
-                    FingerprintUtils.getLegacyInstance(mSensorId),
-                    mSensorProperties.sensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext, mAuthenticatorIds);
-            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+            if (Flags.deHidl()) {
+                scheduleRemoveAidl(token, receiver, 0 /* fingerId */, userId, opPackageName);
+            } else {
+                scheduleRemoveHidl(token, receiver, 0 /* fingerId */, userId, opPackageName);
+            }
         });
     }
 
@@ -755,17 +990,41 @@
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
-            final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
-                    mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
-                    mSensorProperties.sensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                    mBiometricContext,
-                    FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
-            mScheduler.scheduleClientMonitor(client, callback);
+            if (Flags.deHidl()) {
+                scheduleInternalCleanupAidl(userId, callback);
+            } else {
+                scheduleInternalCleanupHidl(userId, callback);
+            }
         });
     }
 
+    private void scheduleInternalCleanupHidl(int userId,
+            @Nullable ClientMonitorCallback callback) {
+        final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
+                mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
+                mSensorProperties.sensorId,
+                createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                mBiometricContext,
+                FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
+        mScheduler.scheduleClientMonitor(client, callback);
+    }
+
+    private void scheduleInternalCleanupAidl(int userId,
+            @Nullable ClientMonitorCallback callback) {
+        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient
+                client =
+                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient(
+                        mContext, this::getSession, userId, mContext.getOpPackageName(),
+                        mSensorProperties.sensorId,
+                        createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector),
+                        mBiometricContext,
+                        FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
+        mScheduler.scheduleClientMonitor(client, callback);
+    }
+
     @Override
     public void scheduleInternalCleanup(int sensorId, int userId,
             @Nullable ClientMonitorCallback callback) {
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
new file mode 100644
index 0000000..c3e5cbe
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
@@ -0,0 +1,95 @@
+/*
+ * 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.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+
+import java.util.ArrayList;
+
+/**
+ * Convert HIDL-specific callback interface {@link IBiometricsFingerprintClientCallback} to AIDL
+ * response handler.
+ */
+public class HidlToAidlCallbackConverter extends IBiometricsFingerprintClientCallback.Stub {
+
+    final AidlResponseHandler mAidlResponseHandler;
+
+    public HidlToAidlCallbackConverter(@NonNull AidlResponseHandler aidlResponseHandler) {
+        mAidlResponseHandler = aidlResponseHandler;
+    }
+
+    @Override
+    public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
+        mAidlResponseHandler.onEnrollmentProgress(fingerId, remaining);
+    }
+
+    @Override
+    public void onAcquired(long deviceId, int acquiredInfo, int vendorCode) {
+        onAcquired_2_2(deviceId, acquiredInfo, vendorCode);
+    }
+
+    @Override
+    public void onAcquired_2_2(long deviceId, int acquiredInfo, int vendorCode) {
+        mAidlResponseHandler.onAcquired(acquiredInfo, vendorCode);
+    }
+
+    @Override
+    public void onAuthenticated(long deviceId, int fingerId, int groupId,
+            ArrayList<Byte> token) {
+        if (fingerId != 0) {
+            byte[] hardwareAuthToken = new byte[token.size()];
+            for (int i = 0; i < token.size(); i++) {
+                hardwareAuthToken[i] = token.get(i);
+            }
+            mAidlResponseHandler.onAuthenticationSucceeded(fingerId,
+                    HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken));
+        } else {
+            mAidlResponseHandler.onAuthenticationFailed();
+        }
+    }
+
+    @Override
+    public void onError(long deviceId, int error, int vendorCode) {
+        mAidlResponseHandler.onError(error, vendorCode);
+    }
+
+    @Override
+    public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
+        mAidlResponseHandler.onEnrollmentsRemoved(new int[]{fingerId});
+    }
+
+    @Override
+    public void onEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
+        mAidlResponseHandler.onEnrollmentsEnumerated(new int[]{fingerId});
+    }
+
+    void onChallengeGenerated(long challenge) {
+        mAidlResponseHandler.onChallengeGenerated(challenge);
+    }
+
+    void onChallengeRevoked(long challenge) {
+        mAidlResponseHandler.onChallengeRevoked(challenge);
+    }
+
+    void onResetLockout() {
+        mAidlResponseHandler.onLockoutCleared();
+    }
+}
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 36d56c8..0730c67 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
@@ -89,7 +89,8 @@
     // Attempt counter should only be cleared when Keyguard goes away or when
     // a biometric is successfully authenticated. Lockout should eventually be done below the HAL.
     // See AuthenticationClient#shouldFrameworkHandleLockout().
-    void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {
+    @Override
+    public void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {
         if (getLockoutModeForUser(userId) != LOCKOUT_NONE) {
             Slog.v(TAG, "Reset biometric lockout for user: " + userId
                     + ", clearAttemptCounter: " + clearAttemptCounter);
@@ -104,7 +105,8 @@
         mLockoutResetCallback.onLockoutReset(userId);
     }
 
-    void addFailedAttemptForUser(int userId) {
+    @Override
+    public void addFailedAttemptForUser(int userId) {
         mFailedAttempts.put(userId, mFailedAttempts.get(userId, 0) + 1);
         mTimedLockoutCleared.put(userId, false);
 
@@ -114,7 +116,8 @@
     }
 
     @Override
-    public @LockoutMode int getLockoutModeForUser(int userId) {
+    @LockoutMode
+    public int getLockoutModeForUser(int userId) {
         final int failedAttempts = mFailedAttempts.get(userId, 0);
         if (failedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {
             return LOCKOUT_PERMANENT;
@@ -126,6 +129,19 @@
         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);
+    }
+
     private void cancelLockoutResetForUser(int userId) {
         mAlarmManager.cancel(getLockoutResetIntentForUser(userId));
     }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index d372f30..0689478 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -160,6 +160,7 @@
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.layout.Layout;
 import com.android.server.display.mode.DisplayModeDirector;
+import com.android.server.display.notifications.DisplayNotificationManager;
 import com.android.server.display.utils.SensorUtils;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.utils.FoldSettingProvider;
@@ -522,6 +523,8 @@
 
     private final DisplayManagerFlags mFlags;
 
+    private final DisplayNotificationManager mDisplayNotificationManager;
+
     /**
      * Applications use {@link android.view.Display#getRefreshRate} and
      * {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate.
@@ -555,6 +558,7 @@
         mInjector = injector;
         mContext = context;
         mFlags = injector.getFlags();
+        mDisplayNotificationManager = new DisplayNotificationManager(mFlags, mContext);
         mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper());
         mUiHandler = UiThread.getHandler();
         mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
@@ -650,6 +654,7 @@
             }
             mDisplayModeDirector.onBootCompleted();
             mLogicalDisplayMapper.onBootCompleted();
+            mDisplayNotificationManager.onBootCompleted();
         }
     }
 
@@ -784,6 +789,10 @@
         }
     }
 
+    DisplayNotificationManager getDisplayNotificationManager() {
+        return mDisplayNotificationManager;
+    }
+
     private void loadStableDisplayValuesLocked() {
         final Point size = mPersistentDataStore.getStableDisplaySize();
         if (size.x > 0 && size.y > 0) {
@@ -1776,7 +1785,8 @@
         synchronized (mSyncRoot) {
             // main display adapter
             registerDisplayAdapterLocked(mInjector.getLocalDisplayAdapter(mSyncRoot, mContext,
-                    mHandler, mDisplayDeviceRepo, mFlags));
+                    mHandler, mDisplayDeviceRepo, mFlags,
+                    mDisplayNotificationManager));
 
             // Standalone VR devices rely on a virtual display as their primary display for
             // 2D UI. We register virtual display adapter along side the main display adapter
@@ -3191,9 +3201,10 @@
 
         LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
                 Handler handler, DisplayAdapter.Listener displayAdapterListener,
-                DisplayManagerFlags flags) {
+                DisplayManagerFlags flags,
+                DisplayNotificationManager displayNotificationManager) {
             return new LocalDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
-                    flags);
+                    flags, displayNotificationManager);
         }
 
         long getDefaultDisplayDelayTimeout() {
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index 9b022d8..5e6ff46 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -30,6 +30,7 @@
 
 class DisplayManagerShellCommand extends ShellCommand {
     private static final String TAG = "DisplayManagerShellCommand";
+    private static final String NOTIFICATION_TYPES = "on-hotplug-error";
 
     private final DisplayManagerService mService;
     private final DisplayManagerFlags mFlags;
@@ -46,6 +47,10 @@
         }
         final PrintWriter pw = getOutPrintWriter();
         switch(cmd) {
+            case "show-notification":
+                return showNotification();
+            case "cancel-notifications":
+                return cancelNotifications();
             case "set-brightness":
                 return setBrightness();
             case "reset-brightness-configuration":
@@ -102,6 +107,10 @@
         pw.println("  help");
         pw.println("    Print this help text.");
         pw.println();
+        pw.println("  show-notification NOTIFICATION_TYPE");
+        pw.println("    Show notification for one of the following types: " + NOTIFICATION_TYPES);
+        pw.println("  cancel-notifications");
+        pw.println("    Cancel notifications.");
         pw.println("  set-brightness BRIGHTNESS");
         pw.println("    Sets the current brightness to BRIGHTNESS (a number between 0 and 1).");
         pw.println("  reset-brightness-configuration");
@@ -172,6 +181,33 @@
         return 0;
     }
 
+    private int showNotification() {
+        final String notificationType = getNextArg();
+        if (notificationType == null) {
+            getErrPrintWriter().println("Error: no notificationType specified, use one of: "
+                                                + NOTIFICATION_TYPES);
+            return 1;
+        }
+
+        switch(notificationType) {
+            case "on-hotplug-error":
+                mService.getDisplayNotificationManager().onHotplugConnectionError();
+                break;
+            default:
+                getErrPrintWriter().println(
+                        "Error: unexpected notification type=" + notificationType + ", use one of: "
+                                + NOTIFICATION_TYPES);
+                return 1;
+        }
+
+        return 0;
+    }
+
+    private int cancelNotifications() {
+        mService.getDisplayNotificationManager().cancelNotifications();
+        return 0;
+    }
+
     private int setBrightness() {
         String brightnessText = getNextArg();
         if (brightnessText == null) {
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 0a1f316..e5d38cb 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -22,9 +22,8 @@
 import android.app.ActivityThread;
 import android.content.Context;
 import android.content.res.Resources;
-import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.DisplayManagerInternal.DisplayOffloader;
 import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloader;
 import android.hardware.sidekick.SidekickInternal;
 import android.os.Build;
 import android.os.Handler;
@@ -52,6 +51,7 @@
 import com.android.server.LocalServices;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.mode.DisplayModeDirector;
+import com.android.server.display.notifications.DisplayNotificationManager;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
 
@@ -86,18 +86,25 @@
 
     private final DisplayManagerFlags mFlags;
 
+    private final DisplayNotificationManager mDisplayNotificationManager;
+
     private Context mOverlayContext;
 
     // Called with SyncRoot lock held.
     LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context,
-            Handler handler, Listener listener, DisplayManagerFlags flags) {
-        this(syncRoot, context, handler, listener, flags, new Injector());
+            Handler handler, Listener listener, DisplayManagerFlags flags,
+            DisplayNotificationManager displayNotificationManager) {
+        this(syncRoot, context, handler, listener, flags, displayNotificationManager,
+                new Injector());
     }
 
     @VisibleForTesting
     LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler,
-            Listener listener, DisplayManagerFlags flags, Injector injector) {
+            Listener listener, DisplayManagerFlags flags,
+            DisplayNotificationManager displayNotificationManager,
+            Injector injector) {
         super(syncRoot, context, handler, listener, TAG);
+        mDisplayNotificationManager = displayNotificationManager;
         mInjector = injector;
         mSurfaceControlProxy = mInjector.getSurfaceControlProxy();
         mIsBootDisplayModeSupported = mSurfaceControlProxy.getBootDisplayModeSupport();
@@ -1454,6 +1461,8 @@
                         + "timestampNanos=" + timestampNanos
                         + ", connectionError=" + connectionError + ")");
             }
+
+            mDisplayNotificationManager.onHotplugConnectionError();
         }
 
         @Override
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 03b0cfc..e3aa161 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -234,7 +234,9 @@
         }
     }
 
-    @VisibleForTesting void onUserChanged(int userHandle) {
+    // should be called in handler thread (same thread that started animation)
+    @VisibleForTesting
+    void onUserChanged(int userHandle) {
         final ContentResolver cr = getContext().getContentResolver();
 
         if (mCurrentUser != UserHandle.USER_NULL) {
@@ -473,6 +475,15 @@
         }
     }
 
+    // should be called in handler thread (same thread that started animation)
+    @VisibleForTesting
+    void cancelAllAnimators() {
+        mNightDisplayTintController.cancelAnimator();
+        mGlobalSaturationTintController.cancelAnimator();
+        mReduceBrightColorsTintController.cancelAnimator();
+        mDisplayWhiteBalanceTintController.cancelAnimator();
+    }
+
     private boolean resetReduceBrightColors() {
         if (mCurrentUser == UserHandle.USER_NULL) {
             return false;
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index a5e3b708..7050c5a 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -59,6 +59,10 @@
             Flags.FLAG_ENABLE_MODE_LIMIT_FOR_EXTERNAL_DISPLAY,
             Flags::enableModeLimitForExternalDisplay);
 
+    private final FlagState mConnectedDisplayErrorHandlingFlagState = new FlagState(
+            Flags.FLAG_ENABLE_CONNECTED_DISPLAY_ERROR_HANDLING,
+            Flags::enableConnectedDisplayErrorHandling);
+
     private final FlagState mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState = new FlagState(
             Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE,
             Flags::backUpSmoothDisplayAndForcePeakRefreshRate);
@@ -123,6 +127,11 @@
         return mDisplayOffloadFlagState.isEnabled();
     }
 
+    /** Returns whether error notifications for connected displays are enabled on not */
+    public boolean isConnectedDisplayErrorHandlingEnabled() {
+        return mConnectedDisplayErrorHandlingFlagState.isEnabled();
+    }
+
     public boolean isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled() {
         return mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState.isEnabled();
     }
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 3d203fb..a85e10d 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -82,6 +82,14 @@
 }
 
 flag {
+    name: "enable_connected_display_error_handling"
+    namespace: "display_manager"
+    description: "Feature flag for connected display error handling"
+    bug: "283461472"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "back_up_smooth_display_and_force_peak_refresh_rate"
     namespace: "display_manager"
     description: "Feature flag for backing up Smooth Display and Force Peak Refresh Rate"
diff --git a/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
new file mode 100644
index 0000000..7d83e90
--- /dev/null
+++ b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
@@ -0,0 +1,150 @@
+/*
+ * 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.notifications;
+
+import static android.app.Notification.COLOR_DEFAULT;
+import static com.android.internal.notification.SystemNotificationChannels.ALERTS;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.feature.DisplayManagerFlags;
+
+/**
+ * Manages notifications for {@link com.android.server.display.DisplayManagerService}.
+ */
+public class DisplayNotificationManager {
+    /** Dependency injection interface for {@link DisplayNotificationManager} */
+    public interface Injector {
+        /** Get {@link NotificationManager} service or null if not available. */
+        @Nullable
+        NotificationManager getNotificationManager();
+    }
+
+    private static final String TAG = "DisplayNotificationManager";
+    private static final String NOTIFICATION_GROUP_NAME = TAG;
+    private static final String DISPLAY_NOTIFICATION_TAG = TAG;
+    private static final int DISPLAY_NOTIFICATION_ID = 1;
+    private static final long NOTIFICATION_TIMEOUT_MILLISEC = 30000L;
+
+    private final Injector mInjector;
+    private final Context mContext;
+    private final boolean mConnectedDisplayErrorHandlingEnabled;
+    private NotificationManager mNotificationManager;
+
+    public DisplayNotificationManager(final DisplayManagerFlags flags, final Context context) {
+        this(flags, context, () -> context.getSystemService(NotificationManager.class));
+    }
+
+    @VisibleForTesting
+    DisplayNotificationManager(final DisplayManagerFlags flags, final Context context,
+            final Injector injector) {
+        mConnectedDisplayErrorHandlingEnabled = flags.isConnectedDisplayErrorHandlingEnabled();
+        mContext = context;
+        mInjector = injector;
+    }
+
+    /**
+     * Initialize services, which may be not yet published during boot.
+     * see {@link android.os.ServiceManager.ServiceNotFoundException}.
+     */
+    public void onBootCompleted() {
+        mNotificationManager = mInjector.getNotificationManager();
+        if (mNotificationManager == null) {
+            Slog.e(TAG, "onBootCompleted: NotificationManager is null");
+            return;
+        }
+    }
+
+    /**
+     * Send notification about hotplug connection error.
+     */
+    public void onHotplugConnectionError() {
+        if (!mConnectedDisplayErrorHandlingEnabled) {
+            Slog.d(TAG, "onHotplugConnectionError:"
+                                + " mConnectedDisplayErrorHandlingEnabled is false");
+            return;
+        }
+
+        sendErrorNotification(createErrorNotification(
+                R.string.connected_display_unavailable_notification_title,
+                R.string.connected_display_unavailable_notification_content));
+    }
+
+    /**
+     * Cancel sent notifications.
+     */
+    public void cancelNotifications() {
+        if (mNotificationManager == null) {
+            Slog.e(TAG, "Can't cancelNotifications: NotificationManager is null");
+            return;
+        }
+
+        mNotificationManager.cancel(DISPLAY_NOTIFICATION_TAG, DISPLAY_NOTIFICATION_ID);
+    }
+
+    /**
+     * Send generic error notification.
+     */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    private void sendErrorNotification(final Notification notification) {
+        if (mNotificationManager == null) {
+            Slog.e(TAG, "Can't sendErrorNotification: NotificationManager is null");
+            return;
+        }
+
+        mNotificationManager.notify(DISPLAY_NOTIFICATION_TAG, DISPLAY_NOTIFICATION_ID,
+                notification);
+    }
+
+    /**
+     * @return a newly built notification about an issue with connected display.
+     */
+    private Notification createErrorNotification(final int titleId, final int messageId) {
+        final Resources resources = mContext.getResources();
+        final CharSequence title = resources.getText(titleId);
+        final CharSequence message = resources.getText(messageId);
+
+        int color = COLOR_DEFAULT;
+        try (var attrs = mContext.obtainStyledAttributes(new int[]{R.attr.colorError})) {
+            color = attrs.getColor(0, color);
+        } catch (Resources.NotFoundException e) {
+            Slog.e(TAG, "colorError attribute is not found: " + e.getMessage());
+        }
+
+        return new Notification.Builder(mContext, ALERTS)
+                .setGroup(NOTIFICATION_GROUP_NAME)
+                .setSmallIcon(R.drawable.usb_cable_unknown_issue)
+                .setWhen(0)
+                .setTimeoutAfter(NOTIFICATION_TIMEOUT_MILLISEC)
+                .setOngoing(false)
+                .setTicker(title)
+                .setColor(color)
+                .setContentTitle(title)
+                .setContentText(message)
+                .setVisibility(Notification.VISIBILITY_PUBLIC)
+                .setCategory(Notification.CATEGORY_ERROR)
+                .build();
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index b090334..27e1b9a 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -60,84 +60,99 @@
     private static final long TIMEOUT_MS = 10_000;
 
     /** Handler for registering timeouts for live entries. */
+    @GuardedBy("mLock")
     private final Handler mHandler;
 
     /** Singleton instance of the History. */
-    @GuardedBy("ImeTrackerService.this")
+    @GuardedBy("mLock")
     private final History mHistory = new History();
 
+    private final Object mLock = new Object();
+
     ImeTrackerService(@NonNull Looper looper) {
         mHandler = new Handler(looper, null /* callback */, true /* async */);
     }
 
     @NonNull
     @Override
-    public synchronized ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
+    public ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
             @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
         final var binder = new Binder();
         final var token = new ImeTracker.Token(binder, tag);
         final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_SHOW, ImeTracker.STATUS_RUN,
                 origin, reason);
-        mHistory.addEntry(binder, entry);
+        synchronized (mLock) {
+            mHistory.addEntry(binder, entry);
 
-        // Register a delayed task to handle the case where the new entry times out.
-        mHandler.postDelayed(() -> {
-            synchronized (ImeTrackerService.this) {
-                mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
-            }
-        }, TIMEOUT_MS);
-
+            // Register a delayed task to handle the case where the new entry times out.
+            mHandler.postDelayed(() -> {
+                synchronized (mLock) {
+                    mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT,
+                            ImeTracker.PHASE_NOT_SET);
+                }
+            }, TIMEOUT_MS);
+        }
         return token;
     }
 
     @NonNull
     @Override
-    public synchronized ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
+    public ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
             @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
         final var binder = new Binder();
         final var token = new ImeTracker.Token(binder, tag);
         final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_HIDE, ImeTracker.STATUS_RUN,
                 origin, reason);
-        mHistory.addEntry(binder, entry);
+        synchronized (mLock) {
+            mHistory.addEntry(binder, entry);
 
-        // Register a delayed task to handle the case where the new entry times out.
-        mHandler.postDelayed(() -> {
-            synchronized (ImeTrackerService.this) {
-                mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
-            }
-        }, TIMEOUT_MS);
-
+            // Register a delayed task to handle the case where the new entry times out.
+            mHandler.postDelayed(() -> {
+                synchronized (mLock) {
+                    mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT,
+                            ImeTracker.PHASE_NOT_SET);
+                }
+            }, TIMEOUT_MS);
+        }
         return token;
     }
 
     @Override
-    public synchronized void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
-        final var entry = mHistory.getEntry(binder);
-        if (entry == null) return;
+    public void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
+        synchronized (mLock) {
+            final var entry = mHistory.getEntry(binder);
+            if (entry == null) return;
 
-        entry.mPhase = phase;
+            entry.mPhase = phase;
+        }
     }
 
     @Override
-    public synchronized void onFailed(@NonNull ImeTracker.Token statsToken,
-            @ImeTracker.Phase int phase) {
-        mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
+    public void onFailed(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
+        synchronized (mLock) {
+            mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
+        }
     }
 
     @Override
-    public synchronized void onCancelled(@NonNull ImeTracker.Token statsToken,
-            @ImeTracker.Phase int phase) {
-        mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
+    public void onCancelled(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
+        synchronized (mLock) {
+            mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
+        }
     }
 
     @Override
-    public synchronized void onShown(@NonNull ImeTracker.Token statsToken) {
-        mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+    public void onShown(@NonNull ImeTracker.Token statsToken) {
+        synchronized (mLock) {
+            mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+        }
     }
 
     @Override
-    public synchronized void onHidden(@NonNull ImeTracker.Token statsToken) {
-        mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+    public void onHidden(@NonNull ImeTracker.Token statsToken) {
+        synchronized (mLock) {
+            mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+        }
     }
 
     /**
@@ -146,25 +161,30 @@
      * @param statsToken the token corresponding to the current IME request.
      * @param requestWindowName the name of the window that created the IME request.
      */
-    public synchronized void onImmsUpdate(@NonNull ImeTracker.Token statsToken,
+    public void onImmsUpdate(@NonNull ImeTracker.Token statsToken,
             @NonNull String requestWindowName) {
-        final var entry = mHistory.getEntry(statsToken.getBinder());
-        if (entry == null) return;
+        synchronized (mLock) {
+            final var entry = mHistory.getEntry(statsToken.getBinder());
+            if (entry == null) return;
 
-        entry.mRequestWindowName = requestWindowName;
+            entry.mRequestWindowName = requestWindowName;
+        }
     }
 
     /** Dumps the contents of the history. */
-    public synchronized void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-        mHistory.dump(pw, prefix);
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        synchronized (mLock) {
+            mHistory.dump(pw, prefix);
+        }
     }
 
     @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
     @Override
-    public synchronized boolean hasPendingImeVisibilityRequests() {
+    public boolean hasPendingImeVisibilityRequests() {
         super.hasPendingImeVisibilityRequests_enforcePermission();
-
-        return !mHistory.mLiveEntries.isEmpty();
+        synchronized (mLock) {
+            return !mHistory.mLiveEntries.isEmpty();
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 1c5ecb7..1e8b387 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -45,7 +45,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 95ca08c..a158b18 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -97,20 +97,23 @@
 public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl {
 
     /**
-     * {@link MediaSession#setMediaButtonBroadcastReceiver(ComponentName)} throws an {@link
-     * IllegalArgumentException} if the provided {@link ComponentName} does not resolve to a valid
-     * {@link android.content.BroadcastReceiver broadcast receiver} for apps targeting Android U and
-     * above. For apps targeting Android T and below, the request will be ignored.
+     * {@link android.media.session.MediaSession#setMediaButtonBroadcastReceiver(
+     * android.content.ComponentName)} throws an {@link
+     * java.lang.IllegalArgumentException} if the provided {@link android.content.ComponentName}
+     * does not resolve to a valid {@link android.content.BroadcastReceiver broadcast receiver}
+     * for apps targeting Android U and above. For apps targeting Android T and below, the request
+     * will be ignored.
      */
     @ChangeId
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     static final long THROW_FOR_INVALID_BROADCAST_RECEIVER = 270049379L;
 
     /**
-     * {@link MediaSession#setMediaButtonReceiver(PendingIntent)} throws an {@link
-     * IllegalArgumentException} if the provided {@link PendingIntent} targets an {@link
-     * android.app.Activity activity} for apps targeting Android V and above. For apps targeting
-     * Android U and below, the request will be ignored.
+     * {@link android.media.session.MediaSession#setMediaButtonReceiver(android.app.PendingIntent)}
+     * throws an {@link java.lang.IllegalArgumentException} if the provided
+     * {@link android.app.PendingIntent} targets an {@link android.app.Activity activity} for
+     * apps targeting Android V and above. For apps targeting Android U and below, the request will
+     * be ignored.
      */
     @ChangeId
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 803ab28..0e8f907 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.os.UserHandle.ALL;
 import static android.os.UserHandle.CURRENT;
+
 import static com.android.server.media.MediaKeyDispatcher.KEY_EVENT_LONG_PRESS;
 import static com.android.server.media.MediaKeyDispatcher.isDoubleTapOverridden;
 import static com.android.server.media.MediaKeyDispatcher.isLongPressOverridden;
@@ -1904,6 +1905,15 @@
                                 keyEvent, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
                         return;
                     }
+                    if (Flags.fallbackToDefaultHandlingWhenMediaSessionHasFixedVolumeHandling()
+                            && !record.canHandleVolumeKey()) {
+                        Log.d(TAG, "Session with packageName=" + record.getPackageName()
+                                + " doesn't support volume adjustment."
+                                + " Fallbacks to the default handling.");
+                        dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid, true,
+                                keyEvent, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
+                        return;
+                    }
                     switch (keyEvent.getAction()) {
                         case KeyEvent.ACTION_DOWN: {
                             int direction = 0;
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 13d1662..8cbc368 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -76,6 +76,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.Watchdog;
@@ -134,6 +135,7 @@
 
     private final MediaRouter mMediaRouter;
     private final MediaRouterCallback mMediaRouterCallback;
+    private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
     private MediaRouter.RouteInfo mMediaRouteInfo;
 
     @GuardedBy("mLock")
@@ -160,6 +162,7 @@
         mWmInternal = LocalServices.getService(WindowManagerInternal.class);
         mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
         mMediaRouterCallback = new MediaRouterCallback();
+        mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger();
         Watchdog.getInstance().addMonitor(this);
     }
 
@@ -193,6 +196,10 @@
         Looper createCallbackLooper() {
             return Looper.getMainLooper();
         }
+
+        MediaProjectionMetricsLogger mediaProjectionMetricsLogger() {
+            return MediaProjectionMetricsLogger.getInstance();
+        }
     }
 
     @Override
@@ -372,6 +379,10 @@
             if (mProjectionGrant != null) {
                 // Cache the session details.
                 mProjectionGrant.mSession = incomingSession;
+                mMediaProjectionMetricsLogger.notifyProjectionStateChange(
+                        mProjectionGrant.uid,
+                        FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS,
+                        FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
                 dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession);
             }
             return true;
@@ -818,6 +829,19 @@
         }
 
         @Override // Binder call
+        @EnforcePermission(MANAGE_MEDIA_PROJECTION)
+        public void notifyPermissionRequestStateChange(int hostUid, int state,
+                int sessionCreationSource) {
+            notifyPermissionRequestStateChange_enforcePermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mMediaProjectionMetricsLogger.notifyProjectionStateChange(hostUid, state, sessionCreationSource);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
         public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
             final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
new file mode 100644
index 0000000..f18ecad
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
@@ -0,0 +1,50 @@
+/*
+ * 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.media.projection;
+
+
+import com.android.internal.util.FrameworkStatsLog;
+
+/**
+ * Class for emitting logs describing a MediaProjection session.
+ */
+public class MediaProjectionMetricsLogger {
+    private static MediaProjectionMetricsLogger sSingleton = null;
+
+    public static MediaProjectionMetricsLogger getInstance() {
+        if (sSingleton == null) {
+            sSingleton = new MediaProjectionMetricsLogger();
+        }
+        return sSingleton;
+    }
+
+    void notifyProjectionStateChange(int hostUid, int state, int sessionCreationSource) {
+        write(hostUid, state, sessionCreationSource);
+    }
+
+    private void write(int hostUid, int state, int sessionCreationSource) {
+        FrameworkStatsLog.write(
+                /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED,
+                /* session_id */ 123,
+                /* state */ state,
+                /* previous_state */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN,
+                /* host_uid */ hostUid,
+                /* target_uid */ -1,
+                /* time_since_last_active */ 0,
+                /* creation_source */ sessionCreationSource);
+    }
+}
diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java
index 4c6110b..d002688 100644
--- a/services/core/java/com/android/server/oemlock/OemLockService.java
+++ b/services/core/java/com/android/server/oemlock/OemLockService.java
@@ -35,8 +35,8 @@
 import android.util.Slog;
 
 import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.SystemService;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
 import com.android.server.pm.UserRestrictionsUtils;
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index c7c8136..79c1346 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -569,6 +569,10 @@
 
         @Override
         public void onEvent(int event, @Nullable String path) {
+            if (path == null) {
+                Slog.w(TAG, "path is null at TombstoneWatcher.onEvent()");
+                return;
+            }
             mHandler.post(() -> {
                 // Ignore .tmp files.
                 if (path.endsWith(".tmp")) {
diff --git a/services/core/java/com/android/server/pdb/OWNERS b/services/core/java/com/android/server/pdb/OWNERS
new file mode 100644
index 0000000..6f322ee
--- /dev/null
+++ b/services/core/java/com/android/server/pdb/OWNERS
@@ -0,0 +1,2 @@
+victorhsieh@google.com
+swillden@google.com
diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java
similarity index 98%
rename from services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
rename to services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java
index 21fa9f9..66ad716 100644
--- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.pdb;
 
 /**
  * Internal interface for storing and retrieving persistent data.
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
similarity index 99%
rename from services/core/java/com/android/server/PersistentDataBlockService.java
rename to services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index 754a7ed..b006ac8 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.pdb;
 
 import static com.android.internal.util.Preconditions.checkArgument;
 
@@ -37,6 +37,9 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
 import com.android.server.pm.UserManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.SystemServerInitThreadPool;
+import com.android.server.SystemService;
 
 import libcore.io.IoUtils;
 
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index d16a812..d804e01 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -444,7 +444,7 @@
 
         updateApplicationInfo(info, flags, state);
 
-        initForUser(info, pkg, userId);
+        initForUser(info, pkg, userId, state);
 
         // TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up
         PackageStateUnserialized pkgState = pkgSetting.getTransientState();
@@ -690,7 +690,7 @@
         info.splitDependencies = pkg.getSplitDependencies().size() == 0
                 ? null : pkg.getSplitDependencies();
 
-        initForUser(info, pkg, userId);
+        initForUser(info, pkg, userId, state);
 
         info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
         info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
@@ -1006,7 +1006,7 @@
     }
 
     private static void initForUser(ApplicationInfo output, AndroidPackage input,
-            @UserIdInt int userId) {
+            @UserIdInt int userId, PackageUserStateInternal state) {
         PackageImpl pkg = ((PackageImpl) input);
         String packageName = input.getPackageName();
         output.uid = UserHandle.getUid(userId, UserHandle.getAppId(input.getUid()));
@@ -1016,6 +1016,13 @@
             return;
         }
 
+        if (android.content.pm.Flags.nullableDataDir()
+                && !state.isInstalled() && !state.dataExists()) {
+            // The data dir has been deleted
+            output.dataDir = null;
+            return;
+        }
+
         // For performance reasons, all these paths are built as strings
         if (userId == UserHandle.USER_SYSTEM) {
             output.credentialProtectedDataDir =
@@ -1050,7 +1057,7 @@
     // This duplicates the ApplicationInfo variant because it uses field assignment and the classes
     // don't inherit from each other, unfortunately. Consolidating logic would introduce overhead.
     private static void initForUser(InstrumentationInfo output, AndroidPackage input,
-            @UserIdInt int userId) {
+            @UserIdInt int userId, PackageUserStateInternal state) {
         PackageImpl pkg = ((PackageImpl) input);
         String packageName = input.getPackageName();
         if ("android".equals(packageName)) {
@@ -1058,6 +1065,13 @@
             return;
         }
 
+        if (android.content.pm.Flags.nullableDataDir()
+                && !state.isInstalled() && !state.dataExists()) {
+            // The data dir has been deleted
+            output.dataDir = null;
+            return;
+        }
+
         // For performance reasons, all these paths are built as strings
         if (userId == UserHandle.USER_SYSTEM) {
             output.credentialProtectedDataDir =
@@ -1089,12 +1103,23 @@
         }
     }
 
-    @NonNull
+    /**
+     * Returns the data dir of the app for the target user. Return null if the app isn't installed
+     * on the target user and doesn't have a data dir on the target user.
+     */
+    @Nullable
     public static File getDataDir(PackageStateInternal ps, int userId) {
         if ("android".equals(ps.getPackageName())) {
             return Environment.getDataSystemDirectory();
         }
 
+        if (android.content.pm.Flags.nullableDataDir()
+                && !ps.getUserStateOrDefault(userId).isInstalled()
+                && !ps.getUserStateOrDefault(userId).dataExists()) {
+            // The app has been uninstalled for the user and the data dir has been deleted
+            return null;
+        }
+
         if (ps.isDefaultToDeviceProtectedStorage()
                 && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
             return Environment.getDataUserDePackageDirectory(ps.getVolumeUuid(), userId,
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index d61bebc..add806f 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -1,6 +1,13 @@
 package: "com.android.server.power.optimization"
 
 flag {
+    name: "power_monitor_api"
+    namespace: "power_optimization"
+    description: "Feature flag for ODPM API"
+    bug: "295027807"
+}
+
+flag {
     name: "streamlined_battery_stats"
     namespace: "power_optimization"
     description: "Feature flag for streamlined battery stats"
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index 5609f69..77290fd 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -694,7 +694,7 @@
                     Log.d(TAG, String.format(Locale.ENGLISH,
                             "Monitor=%s timestamp=%d energy=%d"
                                     + " uid=%d noise=%.1f%% returned=%d",
-                            state.powerMonitor.name,
+                            state.powerMonitor.getName(),
                             state.timestampMs,
                             state.energyUws,
                             callingUid,
@@ -728,7 +728,7 @@
         }
 
         for (PowerMonitorState powerMonitorState : powerMonitorStates) {
-            if (powerMonitorState.powerMonitor.type
+            if (powerMonitorState.powerMonitor.getType()
                     == PowerMonitor.POWER_MONITOR_TYPE_CONSUMER) {
                 for (EnergyConsumerResult energyConsumerResult : energyConsumerResults) {
                     if (energyConsumerResult.id == powerMonitorState.id) {
@@ -754,7 +754,7 @@
         }
 
         for (PowerMonitorState powerMonitorState : powerMonitorStates) {
-            if (powerMonitorState.powerMonitor.type
+            if (powerMonitorState.powerMonitor.getType()
                     == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
                 for (EnergyMeasurement energyMeasurement : energyMeasurements) {
                     if (energyMeasurement.id == powerMonitorState.id) {
@@ -773,7 +773,7 @@
             @PowerMonitor.PowerMonitorType int type) {
         int count = 0;
         for (PowerMonitorState monitorState : powerMonitorStates) {
-            if (monitorState.powerMonitor.type == type) {
+            if (monitorState.powerMonitor.getType() == type) {
                 count++;
             }
         }
@@ -785,7 +785,7 @@
         int[] ids = new int[count];
         int index = 0;
         for (PowerMonitorState monitorState : powerMonitorStates) {
-            if (monitorState.powerMonitor.type == type) {
+            if (monitorState.powerMonitor.getType() == type) {
                 ids[index++] = monitorState.id;
             }
         }
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index c9db343..0656a6a 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -174,8 +174,6 @@
     private CallStateHelper mCallStateHelper;
     private KeyguardManager mKeyguardManager;
 
-    private SafetyCenterManager mSafetyCenterManager;
-
     private int mCurrentUser = USER_NULL;
 
     public SensorPrivacyService(Context context) {
@@ -191,7 +189,6 @@
         mTelephonyManager = context.getSystemService(TelephonyManager.class);
         mPackageManagerInternal = getLocalService(PackageManagerInternal.class);
         mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl();
-        mSafetyCenterManager = mContext.getSystemService(SafetyCenterManager.class);
     }
 
     @Override
@@ -656,7 +653,9 @@
             String contentTitle = getUiContext().getString(messageRes);
             Spanned contentText = Html.fromHtml(getUiContext().getString(
                     R.string.sensor_privacy_start_use_notification_content_text, packageLabel), 0);
-            String action = mSafetyCenterManager.isSafetyCenterEnabled()
+            SafetyCenterManager safetyCenterManager =
+                    mContext.getSystemService(SafetyCenterManager.class);
+            String action = safetyCenterManager.isSafetyCenterEnabled()
                     ? Settings.ACTION_PRIVACY_CONTROLS : Settings.ACTION_PRIVACY_SETTINGS;
 
             PendingIntent contentIntent = PendingIntent.getActivity(mContext, sensor,
diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
index bfe34049e..9a9b836 100644
--- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -41,8 +41,8 @@
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.SystemService;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 
 import java.io.ByteArrayInputStream;
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index a5c0fb3..cddc79d 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -1047,6 +1047,16 @@
                     // in use frontends when no available frontend has been found.
                     int priority = getFrontendHighestClientPriority(fr.getOwnerClientId());
                     if (currentLowestPriority > priority) {
+                        // we need to check the max used num if the target frontend type is not
+                        // currently in primary use (and simply blocked due to exclusive group)
+                        ClientProfile targetOwnerProfile = getClientProfile(fr.getOwnerClientId());
+                        int primaryFeId = targetOwnerProfile.getPrimaryFrontend();
+                        FrontendResource primaryFe = getFrontendResource(primaryFeId);
+                        if (fr.getType() != primaryFe.getType()
+                                && isFrontendMaxNumUseReached(fr.getType())) {
+                            continue;
+                        }
+                        // update the target frontend
                         inUseLowestPriorityFrHandle = fr.getHandle();
                         currentLowestPriority = priority;
                         isRequestFromSameProcess = (requestClient.getProcessId()
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
index e4f9607..a346216 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
 import android.os.VibratorInfo;
 import android.os.vibrator.persistence.ParsedVibration;
 import android.os.vibrator.persistence.VibrationXmlParser;
@@ -127,6 +128,10 @@
                     VibrationXmlParser.VibrationXmlParserException,
                     XmlParserException,
                     XmlPullParserException {
+        if (!Flags.hapticFeedbackVibrationOemCustomizationEnabled()) {
+            Slog.d(TAG, "Haptic feedback customization feature is not enabled.");
+            return null;
+        }
         String customizationFile =
                 res.getString(
                         com.android.internal.R.string.config_hapticFeedbackCustomizationFile);
diff --git a/services/core/java/com/android/server/vibrator/OWNERS b/services/core/java/com/android/server/vibrator/OWNERS
index 9afa682..da5a476 100644
--- a/services/core/java/com/android/server/vibrator/OWNERS
+++ b/services/core/java/com/android/server/vibrator/OWNERS
@@ -1,6 +1,5 @@
 # Bug component: 345036
-
+khalilahmad@google.com
 lsandrade@google.com
 michaelwr@google.com
-sbowden@google.com
-khalilahmad@google.com
\ No newline at end of file
+roosa@google.com
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 01ea33f..0718f2f 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2843,7 +2843,7 @@
         WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId);
         WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId);
         boolean systemValid = systemWallpaper != null;
-        boolean lockValid = lockWallpaper != null && !isLockscreenLiveWallpaperEnabled();
+        boolean lockValid = lockWallpaper != null && isLockscreenLiveWallpaperEnabled();
         return systemValid && lockValid ? new WallpaperData[]{systemWallpaper, lockWallpaper}
                 : systemValid ? new WallpaperData[]{systemWallpaper}
                 : lockValid ? new WallpaperData[]{lockWallpaper}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c866dd0..ebd21d9 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8264,7 +8264,11 @@
 
     private void clearSizeCompatModeAttributes() {
         mInSizeCompatModeForBounds = false;
+        final float lastSizeCompatScale = mSizeCompatScale;
         mSizeCompatScale = 1f;
+        if (mSizeCompatScale != lastSizeCompatScale) {
+            forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
+        }
         mSizeCompatBounds = null;
         mCompatDisplayInsets = null;
         mLetterboxUiController.clearInheritedCompatDisplayInsets();
@@ -8272,11 +8276,7 @@
 
     @VisibleForTesting
     void clearSizeCompatMode() {
-        final float lastSizeCompatScale = mSizeCompatScale;
         clearSizeCompatModeAttributes();
-        if (mSizeCompatScale != lastSizeCompatScale) {
-            forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
-        }
         // Clear config override in #updateCompatDisplayInsets().
         final int activityType = getActivityType();
         final Configuration overrideConfig = getRequestedOverrideConfiguration();
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 458786f..f6c3640 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -18,4 +18,4 @@
 yunfanc@google.com
 
 per-file BackgroundActivityStartController.java = set noparent
-per-file BackgroundActivityStartController.java = brufino@google.com, ogunwale@google.com, louischang@google.com, lus@google.com
\ No newline at end of file
+per-file BackgroundActivityStartController.java = brufino@google.com, topjohnwu@google.com, achim@google.com, ogunwale@google.com, louischang@google.com, lus@google.com
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index f509463..de7871e 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -1067,12 +1067,20 @@
             // legacy sync
             mSyncEngine.startSyncSet(queued.mLegacySync);
         }
-        // Post this so that the now-playing transition logic isn't interrupted.
-        mAtm.mH.post(() -> {
-            synchronized (mAtm.mGlobalLock) {
-                queued.mOnStartCollect.onCollectStarted(true /* deferred */);
-            }
-        });
+        if (queued.mTransition != null
+                && queued.mTransition.mType == WindowManager.TRANSIT_SLEEP) {
+            // SLEEP transitions are special in that they don't collect anything (in fact if they
+            // do collect things it can cause problems). So, we need to run it's onCollectStarted
+            // immediately.
+            queued.mOnStartCollect.onCollectStarted(true /* deferred */);
+        } else {
+            // Post this so that the now-playing transition logic isn't interrupted.
+            mAtm.mH.post(() -> {
+                synchronized (mAtm.mGlobalLock) {
+                    queued.mOnStartCollect.onCollectStarted(true /* deferred */);
+                }
+            });
+        }
     }
 
     void moveToPlaying(Transition transition) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index 00b9b4c..5b9acb2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerFlags.java
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -45,5 +45,7 @@
 
     final boolean mSyncWindowConfigUpdateFlag = Flags.syncWindowConfigUpdateFlag();
 
+    final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag();
+
     /* End Available Flags */
 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4beec2b..726d4d7 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -186,6 +186,7 @@
 import android.app.ActivityTaskManager;
 import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyCache;
+import android.app.servertransaction.WindowStateResizeItem;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Matrix;
@@ -3732,30 +3733,44 @@
 
         markRedrawForSyncReported();
 
-        try {
-            mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
-                    getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
-                    syncWithBuffers ? mSyncSeqId : -1, isDragResizing);
-            if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration
-                    .getMergedConfiguration().windowConfiguration.getRotation()) {
-                mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
-                ProtoLog.v(WM_DEBUG_ORIENTATION,
-                        "Requested redraw for orientation change: %s", this);
+        if (mWmService.mFlags.mWindowStateResizeItemFlag) {
+            getProcess().scheduleClientTransactionItem(
+                    WindowStateResizeItem.obtain(mClient, mClientWindowFrames, reportDraw,
+                            mLastReportedConfiguration, getCompatInsetsState(), forceRelayout,
+                            alwaysConsumeSystemBars, displayId,
+                            syncWithBuffers ? mSyncSeqId : -1, isDragResizing));
+            onResizePostDispatched(drawPending, prevRotation, displayId);
+        } else {
+            // TODO(b/301870955): cleanup after launch
+            try {
+                mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
+                        getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
+                        syncWithBuffers ? mSyncSeqId : -1, isDragResizing);
+                onResizePostDispatched(drawPending, prevRotation, displayId);
+            } catch (RemoteException e) {
+                // Cancel orientation change of this window to avoid blocking unfreeze display.
+                setOrientationChanging(false);
+                mLastFreezeDuration = (int) (SystemClock.elapsedRealtime()
+                        - mWmService.mDisplayFreezeTime);
+                Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e);
             }
-
-            if (mWmService.mAccessibilityController.hasCallbacks()) {
-                mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId);
-            }
-        } catch (RemoteException e) {
-            // Cancel orientation change of this window to avoid blocking unfreeze display.
-            setOrientationChanging(false);
-            mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
-                    - mWmService.mDisplayFreezeTime);
-            Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e);
         }
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
+    private void onResizePostDispatched(boolean drawPending, int prevRotation, int displayId) {
+        if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration
+                .getMergedConfiguration().windowConfiguration.getRotation()) {
+            mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
+            ProtoLog.v(WM_DEBUG_ORIENTATION,
+                    "Requested redraw for orientation change: %s", this);
+        }
+
+        if (mWmService.mAccessibilityController.hasCallbacks()) {
+            mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId);
+        }
+    }
+
     boolean inRelaunchingActivity() {
         return mActivityRecord != null && mActivityRecord.isRelaunching();
     }
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index bc70658..709d5e3 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -70,7 +70,7 @@
         "com_android_server_UsbHostManager.cpp",
         "com_android_server_vibrator_VibratorController.cpp",
         "com_android_server_vibrator_VibratorManagerService.cpp",
-        "com_android_server_PersistentDataBlockService.cpp",
+        "com_android_server_pdb_PersistentDataBlockService.cpp",
         "com_android_server_am_LowMemDetector.cpp",
         "com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",
         "com_android_server_sensor_SensorService.cpp",
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 7e8ce60..0e45f61 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -20,6 +20,7 @@
 per-file com_android_server_location_* = file:/location/java/android/location/OWNERS
 per-file com_android_server_locksettings_* = file:/services/core/java/com/android/server/locksettings/OWNERS
 per-file com_android_server_net_* = file:/services/core/java/com/android/server/net/OWNERS
+per-file com_android_server_pdb_* = file:/services/core/java/com/android/server/pdb/OWNERS
 per-file com_android_server_pm_* = file:/services/core/java/com/android/server/pm/OWNERS
 per-file com_android_server_power_* = file:/services/core/java/com/android/server/power/OWNERS
 per-file com_android_server_powerstats_* = file:/services/core/java/com/android/server/powerstats/OWNERS
diff --git a/services/core/jni/com_android_server_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp
similarity index 85%
rename from services/core/jni/com_android_server_PersistentDataBlockService.cpp
rename to services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp
index 97e69fb..fc5a113 100644
--- a/services/core/jni/com_android_server_PersistentDataBlockService.cpp
+++ b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp
@@ -76,7 +76,7 @@
         return ret;
     }
 
-    static jlong com_android_server_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath)
+    static jlong com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath)
     {
         ScopedUtfChars path(env, jpath);
         int fd = open(path.c_str(), O_RDONLY);
@@ -91,7 +91,7 @@
         return size;
     }
 
-    static int com_android_server_PersistentDataBlockService_wipe(JNIEnv *env, jclass, jstring jpath) {
+    static int com_android_server_pdb_PersistentDataBlockService_wipe(JNIEnv *env, jclass, jstring jpath) {
         ScopedUtfChars path(env, jpath);
         int fd = open(path.c_str(), O_WRONLY);
 
@@ -107,13 +107,13 @@
 
     static const JNINativeMethod sMethods[] = {
          /* name, signature, funcPtr */
-        {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_PersistentDataBlockService_getBlockDeviceSize},
-        {"nativeWipe", "(Ljava/lang/String;)I", (void*)com_android_server_PersistentDataBlockService_wipe},
+        {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize},
+        {"nativeWipe", "(Ljava/lang/String;)I", (void*)com_android_server_pdb_PersistentDataBlockService_wipe},
     };
 
-    int register_android_server_PersistentDataBlockService(JNIEnv* env)
+    int register_android_server_pdb_PersistentDataBlockService(JNIEnv* env)
     {
-        return jniRegisterNativeMethods(env, "com/android/server/PersistentDataBlockService",
+        return jniRegisterNativeMethods(env, "com/android/server/pdb/PersistentDataBlockService",
                                         sMethods, NELEM(sMethods));
     }
 
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index df44895..11734da 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -47,7 +47,7 @@
 int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*);
 int register_android_server_tv_TvUinputBridge(JNIEnv* env);
 int register_android_server_tv_TvInputHal(JNIEnv* env);
-int register_android_server_PersistentDataBlockService(JNIEnv* env);
+int register_android_server_pdb_PersistentDataBlockService(JNIEnv* env);
 int register_android_server_Watchdog(JNIEnv* env);
 int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
 int register_android_server_SyntheticPasswordManager(JNIEnv* env);
@@ -108,7 +108,7 @@
     register_android_server_BatteryStatsService(env);
     register_android_server_tv_TvUinputBridge(env);
     register_android_server_tv_TvInputHal(env);
-    register_android_server_PersistentDataBlockService(env);
+    register_android_server_pdb_PersistentDataBlockService(env);
     register_android_server_HardwarePropertiesManagerService(env);
     register_android_server_storage_AppFuse(env);
     register_android_server_SyntheticPasswordManager(env);
diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp
index 68e2c9a..c736617 100644
--- a/services/core/jni/tvinput/JTvInputHal.cpp
+++ b/services/core/jni/tvinput/JTvInputHal.cpp
@@ -147,7 +147,6 @@
 }
 
 int JTvInputHal::setTvMessageEnabled(int deviceId, int streamId, int type, bool enabled) {
-    Mutex::Autolock autoLock(&mLock);
     if (!mTvInput->setTvMessageEnabled(deviceId, streamId,
                                        static_cast<AidlTvMessageEventType>(type), enabled)
                  .isOk()) {
@@ -188,7 +187,7 @@
 
 void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfoWrapper& info) {
     {
-        Mutex::Autolock autoLock(&mLock);
+        Mutex::Autolock autoLock(&mStreamLock);
         mConnections.add(info.deviceId, KeyedVector<int, Connection>());
     }
     JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -275,7 +274,7 @@
 
 void JTvInputHal::onDeviceUnavailable(int deviceId) {
     {
-        Mutex::Autolock autoLock(&mLock);
+        Mutex::Autolock autoLock(&mStreamLock);
         KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
         for (size_t i = 0; i < connections.size(); ++i) {
             removeStream(deviceId, connections.keyAt(i));
@@ -289,7 +288,7 @@
 
 void JTvInputHal::onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus) {
     {
-        Mutex::Autolock autoLock(&mLock);
+        Mutex::Autolock autoLock(&mStreamLock);
         KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
         for (size_t i = 0; i < connections.size(); ++i) {
             removeStream(deviceId, connections.keyAt(i));
@@ -330,7 +329,7 @@
 void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded) {
     sp<BufferProducerThread> thread;
     {
-        Mutex::Autolock autoLock(&mLock);
+        Mutex::Autolock autoLock(&mStreamLock);
         KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
         Connection& connection = connections.editValueFor(streamId);
         if (connection.mThread == NULL) {
diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h
index b7b4b16..1d8d162 100644
--- a/services/core/jni/tvinput/JTvInputHal.h
+++ b/services/core/jni/tvinput/JTvInputHal.h
@@ -220,7 +220,6 @@
     void onTvMessage(int deviceId, int streamId, AidlTvMessageEventType type,
                      AidlTvMessage& message, signed char data[], int dataLength);
 
-    Mutex mLock;
     Mutex mStreamLock;
     jweak mThiz;
     sp<Looper> mLooper;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 49af89b..5a620a3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -486,7 +486,6 @@
 import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
-import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
@@ -494,6 +493,7 @@
 import com.android.server.devicepolicy.flags.FlagUtils;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 import com.android.server.pm.DefaultCrossProfileIntentFilter;
 import com.android.server.pm.DefaultCrossProfileIntentFiltersUtils;
 import com.android.server.pm.PackageManagerLocal;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 59f1edc..c26aee8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -161,6 +161,7 @@
 import com.android.server.os.DeviceIdentifiersPolicyService;
 import com.android.server.os.NativeTombstoneManagerService;
 import com.android.server.os.SchedulingPolicyService;
+import com.android.server.pdb.PersistentDataBlockService;
 import com.android.server.people.PeopleService;
 import com.android.server.permission.access.AccessCheckingService;
 import com.android.server.pm.ApexManager;
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 486ddb4..a8902fc 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -1391,7 +1391,6 @@
     private static final String[] EMPTY_STRING_ARRAY = new String[0];
 
     private void addLegacyPackageDeviceServer(ServiceInfo serviceInfo, int userId) {
-        Log.d(TAG, "addLegacyPackageDeviceServer()" + userId);
         XmlResourceParser parser = null;
 
         try {
@@ -1529,7 +1528,6 @@
 
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
     private void addUmpPackageDeviceServer(ServiceInfo serviceInfo, int userId) {
-        Log.d(TAG, "addUmpPackageDeviceServer()" + userId);
         XmlResourceParser parser = null;
 
         try {
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index c1d137f..93530cf 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -43,8 +43,7 @@
 
 @Keep
 class AccessCheckingService(context: Context) : SystemService(context) {
-    @Volatile
-    private lateinit var state: AccessState
+    @Volatile private lateinit var state: AccessState
     private val stateLock = Any()
 
     private val policy = AccessPolicy()
@@ -86,17 +85,22 @@
 
         val state = MutableAccessState()
         policy.initialize(
-            state, userIds, packageStates, disabledSystemPackageStates, knownPackages, isLeanback,
-            configPermissions, privilegedPermissionAllowlistPackages, permissionAllowlist,
+            state,
+            userIds,
+            packageStates,
+            disabledSystemPackageStates,
+            knownPackages,
+            isLeanback,
+            configPermissions,
+            privilegedPermissionAllowlistPackages,
+            permissionAllowlist,
             implicitToSourcePermissions
         )
         persistence.initialize()
         persistence.read(state)
         this.state = state
 
-        mutateState {
-            with(policy) { onInitialized() }
-        }
+        mutateState { with(policy) { onInitialized() } }
 
         appOpService.initialize()
         permissionService.initialize()
@@ -106,40 +110,40 @@
         get() = PackageManager.FEATURE_LEANBACK in availableFeatures
 
     private val SystemConfig.privilegedPermissionAllowlistPackages: IndexedListSet<String>
-        get() = MutableIndexedListSet<String>().apply {
-            this += "android"
-            if (PackageManager.FEATURE_AUTOMOTIVE in availableFeatures) {
-                // Note that SystemProperties.get(String, String) forces returning an empty string
-                // even if we pass null for the def parameter.
-                val carServicePackage = SystemProperties.get("ro.android.car.carservice.package")
-                if (carServicePackage.isNotEmpty()) {
-                    this += carServicePackage
+        get() =
+            MutableIndexedListSet<String>().apply {
+                this += "android"
+                if (PackageManager.FEATURE_AUTOMOTIVE in availableFeatures) {
+                    // Note that SystemProperties.get(String, String) forces returning an empty
+                    // string
+                    // even if we pass null for the def parameter.
+                    val carServicePackage =
+                        SystemProperties.get("ro.android.car.carservice.package")
+                    if (carServicePackage.isNotEmpty()) {
+                        this += carServicePackage
+                    }
                 }
             }
-        }
 
     private val SystemConfig.implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>
         @Suppress("UNCHECKED_CAST")
-        get() = MutableIndexedMap<String, MutableIndexedListSet<String>>().apply {
-            splitPermissions.forEach { splitPermissionInfo ->
-                val sourcePermissionName = splitPermissionInfo.splitPermission
-                splitPermissionInfo.newPermissions.forEach { implicitPermissionName ->
-                    getOrPut(implicitPermissionName) { MutableIndexedListSet() } +=
-                        sourcePermissionName
+        get() =
+            MutableIndexedMap<String, MutableIndexedListSet<String>>().apply {
+                splitPermissions.forEach { splitPermissionInfo ->
+                    val sourcePermissionName = splitPermissionInfo.splitPermission
+                    splitPermissionInfo.newPermissions.forEach { implicitPermissionName ->
+                        getOrPut(implicitPermissionName) { MutableIndexedListSet() } +=
+                            sourcePermissionName
+                    }
                 }
-            }
-        } as IndexedMap<String, IndexedListSet<String>>
+            } as IndexedMap<String, IndexedListSet<String>>
 
     internal fun onUserAdded(userId: Int) {
-        mutateState {
-            with(policy) { onUserAdded(userId) }
-        }
+        mutateState { with(policy) { onUserAdded(userId) } }
     }
 
     internal fun onUserRemoved(userId: Int) {
-        mutateState {
-            with(policy) { onUserRemoved(userId) }
-        }
+        mutateState { with(policy) { onUserRemoved(userId) } }
     }
 
     internal fun onStorageVolumeMounted(
@@ -152,8 +156,12 @@
         mutateState {
             with(policy) {
                 onStorageVolumeMounted(
-                    packageStates, disabledSystemPackageStates, knownPackages, volumeUuid,
-                    packageNames, isSystemUpdated
+                    packageStates,
+                    disabledSystemPackageStates,
+                    knownPackages,
+                    volumeUuid,
+                    packageNames,
+                    isSystemUpdated
                 )
             }
         }
@@ -165,7 +173,10 @@
         mutateState {
             with(policy) {
                 onPackageAdded(
-                    packageStates, disabledSystemPackageStates, knownPackages, packageName
+                    packageStates,
+                    disabledSystemPackageStates,
+                    knownPackages,
+                    packageName
                 )
             }
         }
@@ -177,7 +188,11 @@
         mutateState {
             with(policy) {
                 onPackageRemoved(
-                    packageStates, disabledSystemPackageStates, knownPackages, packageName, appId
+                    packageStates,
+                    disabledSystemPackageStates,
+                    knownPackages,
+                    packageName,
+                    appId
                 )
             }
         }
@@ -189,7 +204,11 @@
         mutateState {
             with(policy) {
                 onPackageInstalled(
-                    packageStates, disabledSystemPackageStates, knownPackages, packageName, userId
+                    packageStates,
+                    disabledSystemPackageStates,
+                    knownPackages,
+                    packageName,
+                    userId
                 )
             }
         }
@@ -201,7 +220,11 @@
         mutateState {
             with(policy) {
                 onPackageUninstalled(
-                    packageStates, disabledSystemPackageStates, knownPackages, packageName, appId,
+                    packageStates,
+                    disabledSystemPackageStates,
+                    knownPackages,
+                    packageName,
+                    appId,
                     userId
                 )
             }
@@ -224,34 +247,42 @@
 
     private fun PackageManagerInternal.getKnownPackages(
         packageStates: Map<String, PackageState>
-    ): IntMap<Array<String>> = MutableIntMap<Array<String>>().apply {
-        this[KnownPackages.PACKAGE_INSTALLER] =
-            getKnownPackageNames(KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM)
-        this[KnownPackages.PACKAGE_PERMISSION_CONTROLLER] = getKnownPackageNames(
-            KnownPackages.PACKAGE_PERMISSION_CONTROLLER, UserHandle.USER_SYSTEM
-        )
-        this[KnownPackages.PACKAGE_VERIFIER] =
-            getKnownPackageNames(KnownPackages.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM)
-        this[KnownPackages.PACKAGE_SETUP_WIZARD] =
-            getKnownPackageNames(KnownPackages.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM)
-        this[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER] = getKnownPackageNames(
-            KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER, UserHandle.USER_SYSTEM
-        )
-        this[KnownPackages.PACKAGE_CONFIGURATOR] =
-            getKnownPackageNames(KnownPackages.PACKAGE_CONFIGURATOR, UserHandle.USER_SYSTEM)
-        this[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER] = getKnownPackageNames(
-            KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER, UserHandle.USER_SYSTEM
-        )
-        this[KnownPackages.PACKAGE_APP_PREDICTOR] =
-            getKnownPackageNames(KnownPackages.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM)
-        this[KnownPackages.PACKAGE_COMPANION] =
-            getKnownPackageNames(KnownPackages.PACKAGE_COMPANION, UserHandle.USER_SYSTEM)
-        this[KnownPackages.PACKAGE_RETAIL_DEMO] =
-            getKnownPackageNames(KnownPackages.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM)
-                .filter { isProfileOwner(it, packageStates) }.toTypedArray()
-        this[KnownPackages.PACKAGE_RECENTS] =
-            getKnownPackageNames(KnownPackages.PACKAGE_RECENTS, UserHandle.USER_SYSTEM)
-    }
+    ): IntMap<Array<String>> =
+        MutableIntMap<Array<String>>().apply {
+            this[KnownPackages.PACKAGE_INSTALLER] =
+                getKnownPackageNames(KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM)
+            this[KnownPackages.PACKAGE_PERMISSION_CONTROLLER] =
+                getKnownPackageNames(
+                    KnownPackages.PACKAGE_PERMISSION_CONTROLLER,
+                    UserHandle.USER_SYSTEM
+                )
+            this[KnownPackages.PACKAGE_VERIFIER] =
+                getKnownPackageNames(KnownPackages.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM)
+            this[KnownPackages.PACKAGE_SETUP_WIZARD] =
+                getKnownPackageNames(KnownPackages.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM)
+            this[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER] =
+                getKnownPackageNames(
+                    KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER,
+                    UserHandle.USER_SYSTEM
+                )
+            this[KnownPackages.PACKAGE_CONFIGURATOR] =
+                getKnownPackageNames(KnownPackages.PACKAGE_CONFIGURATOR, UserHandle.USER_SYSTEM)
+            this[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER] =
+                getKnownPackageNames(
+                    KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER,
+                    UserHandle.USER_SYSTEM
+                )
+            this[KnownPackages.PACKAGE_APP_PREDICTOR] =
+                getKnownPackageNames(KnownPackages.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM)
+            this[KnownPackages.PACKAGE_COMPANION] =
+                getKnownPackageNames(KnownPackages.PACKAGE_COMPANION, UserHandle.USER_SYSTEM)
+            this[KnownPackages.PACKAGE_RETAIL_DEMO] =
+                getKnownPackageNames(KnownPackages.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM)
+                    .filter { isProfileOwner(it, packageStates) }
+                    .toTypedArray()
+            this[KnownPackages.PACKAGE_RECENTS] =
+                getKnownPackageNames(KnownPackages.PACKAGE_RECENTS, UserHandle.USER_SYSTEM)
+        }
 
     private fun isProfileOwner(
         packageName: String,
diff --git a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
index a3f65af..d0913d2 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
@@ -38,16 +38,11 @@
 import java.io.File
 import java.io.FileNotFoundException
 
-class AccessPersistence(
-    private val policy: AccessPolicy
-) {
+class AccessPersistence(private val policy: AccessPolicy) {
     private val scheduleLock = Any()
-    @GuardedBy("scheduleLock")
-    private val pendingMutationTimesMillis = SparseLongArray()
-    @GuardedBy("scheduleLock")
-    private val pendingStates = MutableIntMap<AccessState>()
-    @GuardedBy("scheduleLock")
-    private lateinit var writeHandler: WriteHandler
+    @GuardedBy("scheduleLock") private val pendingMutationTimesMillis = SparseLongArray()
+    @GuardedBy("scheduleLock") private val pendingStates = MutableIntMap<AccessState>()
+    @GuardedBy("scheduleLock") private lateinit var writeHandler: WriteHandler
 
     private val writeLock = Any()
 
@@ -60,17 +55,16 @@
      */
     fun read(state: MutableAccessState) {
         readSystemState(state)
-        state.externalState.userIds.forEachIndexed { _, userId ->
-            readUserState(state, userId)
-        }
+        state.externalState.userIds.forEachIndexed { _, userId -> readUserState(state, userId) }
     }
 
     private fun readSystemState(state: MutableAccessState) {
-        val fileExists = systemFile.parse {
-            // This is the canonical way to call an extension function in a different class.
-            // TODO(b/259469752): Use context receiver for this when it becomes stable.
-            with(policy) { parseSystemState(state) }
-        }
+        val fileExists =
+            systemFile.parse {
+                // This is the canonical way to call an extension function in a different class.
+                // TODO(b/259469752): Use context receiver for this when it becomes stable.
+                with(policy) { parseSystemState(state) }
+            }
 
         if (!fileExists) {
             policy.migrateSystemState(state)
@@ -79,9 +73,8 @@
     }
 
     private fun readUserState(state: MutableAccessState, userId: Int) {
-        val fileExists = getUserFile(userId).parse {
-            with(policy) { parseUserState(state, userId) }
-        }
+        val fileExists =
+            getUserFile(userId).parse { with(policy) { parseUserState(state, userId) } }
 
         if (!fileExists) {
             policy.migrateUserState(state, userId)
@@ -90,8 +83,8 @@
     }
 
     /**
-     * @return {@code true} if the file is successfully read from the disk; {@code false} if
-     * the file doesn't exist yet.
+     * @return {@code true} if the file is successfully read from the disk; {@code false} if the
+     *   file doesn't exist yet.
      */
     private inline fun File.parse(block: BinaryXmlPullParser.() -> Unit): Boolean =
         try {
@@ -106,9 +99,7 @@
 
     fun write(state: AccessState) {
         state.systemState.write(state, UserHandle.USER_ALL)
-        state.userStates.forEachIndexed { _, userId, userState ->
-            userState.write(state, userId)
-        }
+        state.userStates.forEachIndexed { _, userId, userState -> userState.write(state, userId) }
     }
 
     private fun WritableState.write(state: AccessState, userId: Int) {
@@ -127,8 +118,10 @@
                     if (currentDelayMillis > MAX_WRITE_DELAY_MILLIS) {
                         message.sendToTarget()
                     } else {
-                        val newDelayMillis = WRITE_DELAY_TIME_MILLIS
-                            .coerceAtMost(MAX_WRITE_DELAY_MILLIS - currentDelayMillis)
+                        val newDelayMillis =
+                            WRITE_DELAY_TIME_MILLIS.coerceAtMost(
+                                MAX_WRITE_DELAY_MILLIS - currentDelayMillis
+                            )
                         writeHandler.sendMessageDelayed(message, newDelayMillis)
                     }
                 }
@@ -161,15 +154,11 @@
     }
 
     private fun writeSystemState(state: AccessState) {
-        systemFile.serialize {
-            with(policy) { serializeSystemState(state) }
-        }
+        systemFile.serialize { with(policy) { serializeSystemState(state) } }
     }
 
     private fun writeUserState(state: AccessState, userId: Int) {
-        getUserFile(userId).serialize {
-            with(policy) { serializeUserState(state, userId) }
-        }
+        getUserFile(userId).serialize { with(policy) { serializeUserState(state, userId) } }
     }
 
     private inline fun File.serialize(block: BinaryXmlSerializer.() -> Unit) {
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 6a349e2..754f77ec 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -37,21 +37,24 @@
 import com.android.server.pm.permission.PermissionAllowlist
 import com.android.server.pm.pkg.PackageState
 
-class AccessPolicy private constructor(
+class AccessPolicy
+private constructor(
     private val schemePolicies: IndexedMap<String, IndexedMap<String, SchemePolicy>>
 ) {
     @Suppress("UNCHECKED_CAST")
-    constructor() : this(
-        MutableIndexedMap<String, MutableIndexedMap<String, SchemePolicy>>().apply {
-            fun addPolicy(policy: SchemePolicy) {
-                getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] = policy
-            }
-            addPolicy(AppIdPermissionPolicy())
-            addPolicy(DevicePermissionPolicy())
-            addPolicy(AppIdAppOpPolicy())
-            addPolicy(PackageAppOpPolicy())
-        } as IndexedMap<String, IndexedMap<String, SchemePolicy>>
-    )
+    constructor() :
+        this(
+            MutableIndexedMap<String, MutableIndexedMap<String, SchemePolicy>>().apply {
+                fun addPolicy(policy: SchemePolicy) {
+                    getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] =
+                        policy
+                }
+                addPolicy(AppIdPermissionPolicy())
+                addPolicy(DevicePermissionPolicy())
+                addPolicy(AppIdAppOpPolicy())
+                addPolicy(PackageAppOpPolicy())
+            } as IndexedMap<String, IndexedMap<String, SchemePolicy>>
+        )
 
     fun getSchemePolicy(subjectScheme: String, objectScheme: String): SchemePolicy =
         checkNotNull(schemePolicies[subjectScheme]?.get(objectScheme)) {
@@ -92,23 +95,17 @@
     }
 
     fun GetStateScope.onStateMutated() {
-        forEachSchemePolicy {
-            with(it) { onStateMutated() }
-        }
+        forEachSchemePolicy { with(it) { onStateMutated() } }
     }
 
     fun MutateStateScope.onInitialized() {
-        forEachSchemePolicy {
-            with(it) { onInitialized() }
-        }
+        forEachSchemePolicy { with(it) { onInitialized() } }
     }
 
     fun MutateStateScope.onUserAdded(userId: Int) {
         newState.mutateExternalState().mutateUserIds() += userId
         newState.mutateUserStatesNoWrite()[userId] = MutableUserState()
-        forEachSchemePolicy {
-            with(it) { onUserAdded(userId) }
-        }
+        forEachSchemePolicy { with(it) { onUserAdded(userId) } }
         newState.externalState.packageStates.forEach { (_, packageState) ->
             upgradePackageVersion(packageState, userId)
         }
@@ -117,9 +114,7 @@
     fun MutateStateScope.onUserRemoved(userId: Int) {
         newState.mutateExternalState().mutateUserIds() -= userId
         newState.mutateUserStatesNoWrite() -= userId
-        forEachSchemePolicy {
-            with(it) { onUserRemoved(userId) }
-        }
+        forEachSchemePolicy { with(it) { onUserRemoved(userId) } }
     }
 
     fun MutateStateScope.onStorageVolumeMounted(
@@ -154,9 +149,7 @@
             setKnownPackages(knownPackages)
         }
         addedAppIds.forEachIndexed { _, appId ->
-            forEachSchemePolicy {
-                with(it) { onAppIdAdded(appId) }
-            }
+            forEachSchemePolicy { with(it) { onAppIdAdded(appId) } }
         }
         forEachSchemePolicy {
             with(it) { onStorageVolumeMounted(volumeUuid, packageNames, isSystemUpdated) }
@@ -192,13 +185,9 @@
             setKnownPackages(knownPackages)
         }
         if (isAppIdAdded) {
-            forEachSchemePolicy {
-                with(it) { onAppIdAdded(appId) }
-            }
+            forEachSchemePolicy { with(it) { onAppIdAdded(appId) } }
         }
-        forEachSchemePolicy {
-            with(it) { onPackageAdded(packageState) }
-        }
+        forEachSchemePolicy { with(it) { onPackageAdded(packageState) } }
         newState.userStates.forEachIndexed { _, userId, _ ->
             upgradePackageVersion(packageState, userId)
         }
@@ -227,13 +216,9 @@
             }
             setKnownPackages(knownPackages)
         }
-        forEachSchemePolicy {
-            with(it) { onPackageRemoved(packageName, appId) }
-        }
+        forEachSchemePolicy { with(it) { onPackageRemoved(packageName, appId) } }
         if (isAppIdRemoved) {
-            forEachSchemePolicy {
-                with(it) { onAppIdRemoved(appId) }
-            }
+            forEachSchemePolicy { with(it) { onAppIdRemoved(appId) } }
         }
         newState.userStates.forEachIndexed { userStateIndex, _, userState ->
             if (packageName in userState.packageVersions) {
@@ -258,9 +243,7 @@
         checkNotNull(packageState) {
             "Installed package $packageName isn't found in packageStates in onPackageInstalled()"
         }
-        forEachSchemePolicy {
-            with(it) { onPackageInstalled(packageState, userId) }
-        }
+        forEachSchemePolicy { with(it) { onPackageInstalled(packageState, userId) } }
     }
 
     fun MutateStateScope.onPackageUninstalled(
@@ -276,9 +259,7 @@
             setDisabledSystemPackageStates(disabledSystemPackageStates)
             setKnownPackages(knownPackages)
         }
-        forEachSchemePolicy {
-            with(it) { onPackageUninstalled(packageName, appId, userId) }
-        }
+        forEachSchemePolicy { with(it) { onPackageUninstalled(packageName, appId, userId) } }
     }
 
     fun MutateStateScope.onSystemReady(
@@ -292,21 +273,15 @@
             setKnownPackages(knownPackages)
             setSystemReady(true)
         }
-        forEachSchemePolicy {
-            with(it) { onSystemReady() }
-        }
+        forEachSchemePolicy { with(it) { onSystemReady() } }
     }
 
     fun migrateSystemState(state: MutableAccessState) {
-        forEachSchemePolicy {
-            with(it) { migrateSystemState(state) }
-        }
+        forEachSchemePolicy { with(it) { migrateSystemState(state) } }
     }
 
     fun migrateUserState(state: MutableAccessState, userId: Int) {
-        forEachSchemePolicy {
-            with(it) { migrateUserState(state, userId) }
-        }
+        forEachSchemePolicy { with(it) { migrateUserState(state, userId) } }
     }
 
     private fun MutateStateScope.upgradePackageVersion(packageState: PackageState, userId: Int) {
@@ -330,10 +305,12 @@
                     VERSION_LATEST
             }
             version == VERSION_LATEST -> {}
-            else -> Slog.w(
-                LOG_TAG, "Unexpected version $version for package $packageName," +
-                    "latest version is $VERSION_LATEST"
-            )
+            else ->
+                Slog.w(
+                    LOG_TAG,
+                    "Unexpected version $version for package $packageName," +
+                        "latest version is $VERSION_LATEST"
+                )
         }
     }
 
@@ -341,11 +318,7 @@
         forEachTag {
             when (tagName) {
                 TAG_ACCESS -> {
-                    forEachTag {
-                        forEachSchemePolicy {
-                            with(it) { parseSystemState(state) }
-                        }
-                    }
+                    forEachTag { forEachSchemePolicy { with(it) { parseSystemState(state) } } }
                 }
                 else -> Slog.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing system state")
             }
@@ -353,11 +326,7 @@
     }
 
     fun BinaryXmlSerializer.serializeSystemState(state: AccessState) {
-        tag(TAG_ACCESS) {
-            forEachSchemePolicy {
-                with(it) { serializeSystemState(state) }
-            }
-        }
+        tag(TAG_ACCESS) { forEachSchemePolicy { with(it) { serializeSystemState(state) } } }
     }
 
     fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
@@ -370,9 +339,7 @@
                             TAG_DEFAULT_PERMISSION_GRANT ->
                                 parseDefaultPermissionGrant(state, userId)
                             else -> {
-                                forEachSchemePolicy {
-                                    with(it) { parseUserState(state, userId) }
-                                }
+                                forEachSchemePolicy { with(it) { parseUserState(state, userId) } }
                             }
                         }
                     }
@@ -428,9 +395,7 @@
             serializeDefaultPermissionGrantFingerprint(
                 state.userStates[userId]!!.defaultPermissionGrantFingerprint
             )
-            forEachSchemePolicy {
-                with(it) { serializeUserState(state, userId) }
-            }
+            forEachSchemePolicy { with(it) { serializeUserState(state, userId) } }
         }
     }
 
@@ -451,9 +416,7 @@
         fingerprint: String?
     ) {
         if (fingerprint != null) {
-            tag(TAG_DEFAULT_PERMISSION_GRANT) {
-                attributeInterned(ATTR_FINGERPRINT, fingerprint)
-            }
+            tag(TAG_DEFAULT_PERMISSION_GRANT) { attributeInterned(ATTR_FINGERPRINT, fingerprint) }
         }
     }
 
@@ -462,9 +425,7 @@
 
     private inline fun forEachSchemePolicy(action: (SchemePolicy) -> Unit) {
         schemePolicies.forEachIndexed { _, _, objectSchemePolicies ->
-            objectSchemePolicies.forEachIndexed { _, _, schemePolicy ->
-                action(schemePolicy)
-            }
+            objectSchemePolicies.forEachIndexed { _, _, schemePolicy -> action(schemePolicy) }
         }
     }
 
diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt
index 94c878a..49d2f81 100644
--- a/services/permission/java/com/android/server/permission/access/AccessState.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -28,7 +28,9 @@
 private typealias SystemStateReference = MutableReference<SystemState, MutableSystemState>
 
 typealias UserStates = IntReferenceMap<UserState, MutableUserState>
+
 typealias MutableUserStates = MutableIntReferenceMap<UserState, MutableUserState>
+
 private typealias UserStatesReference = MutableReference<UserStates, MutableUserStates>
 
 sealed class AccessState(
@@ -48,22 +50,22 @@
     override fun toMutable(): MutableAccessState = MutableAccessState(this)
 }
 
-class MutableAccessState private constructor(
+class MutableAccessState
+private constructor(
     externalStateReference: ExternalStateReference,
     systemStateReference: SystemStateReference,
     userStatesReference: UserStatesReference
-) : AccessState(
-    externalStateReference,
-    systemStateReference,
-    userStatesReference
-) {
-    constructor() : this(
-        ExternalStateReference(MutableExternalState()),
-        SystemStateReference(MutableSystemState()),
-        UserStatesReference(MutableUserStates())
-    )
+) : AccessState(externalStateReference, systemStateReference, userStatesReference) {
+    constructor() :
+        this(
+            ExternalStateReference(MutableExternalState()),
+            SystemStateReference(MutableSystemState()),
+            UserStatesReference(MutableUserStates())
+        )
 
-    internal constructor(accessState: AccessState) : this(
+    internal constructor(
+        accessState: AccessState
+    ) : this(
         accessState.externalStateReference.toImmutable(),
         accessState.systemStateReference.toImmutable(),
         accessState.userStatesReference.toImmutable()
@@ -86,8 +88,10 @@
 private typealias UserIdsReference = MutableReference<IntSet, MutableIntSet>
 
 typealias AppIdPackageNames = IntReferenceMap<IndexedListSet<String>, MutableIndexedListSet<String>>
+
 typealias MutableAppIdPackageNames =
     MutableIntReferenceMap<IndexedListSet<String>, MutableIndexedListSet<String>>
+
 private typealias AppIdPackageNamesReference =
     MutableReference<AppIdPackageNames, MutableAppIdPackageNames>
 
@@ -142,7 +146,8 @@
     override fun toMutable(): MutableExternalState = MutableExternalState(this)
 }
 
-class MutableExternalState private constructor(
+class MutableExternalState
+private constructor(
     userIdsReference: UserIdsReference,
     packageStates: Map<String, PackageState>,
     disabledSystemPackageStates: Map<String, PackageState>,
@@ -154,34 +159,38 @@
     permissionAllowlist: PermissionAllowlist,
     implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>,
     isSystemReady: Boolean
-) : ExternalState(
-    userIdsReference,
-    packageStates,
-    disabledSystemPackageStates,
-    appIdPackageNamesReference,
-    knownPackages,
-    isLeanback,
-    configPermissions,
-    privilegedPermissionAllowlistPackages,
-    permissionAllowlist,
-    implicitToSourcePermissions,
-    isSystemReady
-) {
-    constructor() : this(
-        UserIdsReference(MutableIntSet()),
-        emptyMap(),
-        emptyMap(),
-        AppIdPackageNamesReference(MutableAppIdPackageNames()),
-        MutableIntMap(),
-        false,
-        emptyMap(),
-        MutableIndexedListSet(),
-        PermissionAllowlist(),
-        MutableIndexedMap(),
-        false
-    )
+) :
+    ExternalState(
+        userIdsReference,
+        packageStates,
+        disabledSystemPackageStates,
+        appIdPackageNamesReference,
+        knownPackages,
+        isLeanback,
+        configPermissions,
+        privilegedPermissionAllowlistPackages,
+        permissionAllowlist,
+        implicitToSourcePermissions,
+        isSystemReady
+    ) {
+    constructor() :
+        this(
+            UserIdsReference(MutableIntSet()),
+            emptyMap(),
+            emptyMap(),
+            AppIdPackageNamesReference(MutableAppIdPackageNames()),
+            MutableIntMap(),
+            false,
+            emptyMap(),
+            MutableIndexedListSet(),
+            PermissionAllowlist(),
+            MutableIndexedMap(),
+            false
+        )
 
-    internal constructor(externalState: ExternalState) : this(
+    internal constructor(
+        externalState: ExternalState
+    ) : this(
         externalState.userIdsReference.toImmutable(),
         externalState.packageStates,
         externalState.disabledSystemPackageStates,
@@ -249,9 +258,10 @@
     }
 }
 
-private typealias PermissionGroupsReference = MutableReference<
-    IndexedMap<String, PermissionGroupInfo>, MutableIndexedMap<String, PermissionGroupInfo>
->
+private typealias PermissionGroupsReference =
+    MutableReference<
+        IndexedMap<String, PermissionGroupInfo>, MutableIndexedMap<String, PermissionGroupInfo>
+    >
 
 private typealias PermissionTreesReference =
     MutableReference<IndexedMap<String, Permission>, MutableIndexedMap<String, Permission>>
@@ -280,25 +290,31 @@
     override fun toMutable(): MutableSystemState = MutableSystemState(this)
 }
 
-class MutableSystemState private constructor(
+class MutableSystemState
+private constructor(
     permissionGroupsReference: PermissionGroupsReference,
     permissionTreesReference: PermissionTreesReference,
     permissionsReference: PermissionsReference,
     writeMode: Int
-) : SystemState(
-    permissionGroupsReference,
-    permissionTreesReference,
-    permissionsReference,
-    writeMode
-), MutableWritableState {
-    constructor() : this(
-        PermissionGroupsReference(MutableIndexedMap()),
-        PermissionTreesReference(MutableIndexedMap()),
-        PermissionsReference(MutableIndexedMap()),
-        WriteMode.NONE
-    )
+) :
+    SystemState(
+        permissionGroupsReference,
+        permissionTreesReference,
+        permissionsReference,
+        writeMode
+    ),
+    MutableWritableState {
+    constructor() :
+        this(
+            PermissionGroupsReference(MutableIndexedMap()),
+            PermissionTreesReference(MutableIndexedMap()),
+            PermissionsReference(MutableIndexedMap()),
+            WriteMode.NONE
+        )
 
-    internal constructor(systemState: SystemState) : this(
+    internal constructor(
+        systemState: SystemState
+    ) : this(
         systemState.permissionGroupsReference.toImmutable(),
         systemState.permissionTreesReference.toImmutable(),
         systemState.permissionsReference.toImmutable(),
@@ -311,8 +327,7 @@
     fun mutatePermissionTrees(): MutableIndexedMap<String, Permission> =
         permissionTreesReference.mutate()
 
-    fun mutatePermissions(): MutableIndexedMap<String, Permission> =
-        permissionsReference.mutate()
+    fun mutatePermissions(): MutableIndexedMap<String, Permission> = permissionsReference.mutate()
 
     override fun requestWriteMode(writeMode: Int) {
         this.writeMode = maxOf(this.writeMode, writeMode)
@@ -324,34 +339,42 @@
 
 typealias AppIdPermissionFlags =
     IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+
 typealias MutableAppIdPermissionFlags =
     MutableIntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+
 private typealias AppIdPermissionFlagsReference =
     MutableReference<AppIdPermissionFlags, MutableAppIdPermissionFlags>
 
-
 typealias DevicePermissionFlags =
     IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+
 typealias MutableDevicePermissionFlags =
     MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+
 typealias AppIdDevicePermissionFlags =
     IntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags>
+
 typealias MutableAppIdDevicePermissionFlags =
     MutableIntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags>
+
 private typealias AppIdDevicePermissionFlagsReference =
     MutableReference<AppIdDevicePermissionFlags, MutableAppIdDevicePermissionFlags>
 
-typealias AppIdAppOpModes =
-    IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+typealias AppIdAppOpModes = IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+
 typealias MutableAppIdAppOpModes =
     MutableIntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+
 private typealias AppIdAppOpModesReference =
     MutableReference<AppIdAppOpModes, MutableAppIdAppOpModes>
 
 typealias PackageAppOpModes =
     IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+
 typealias MutablePackageAppOpModes =
     MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+
 private typealias PackageAppOpModesReference =
     MutableReference<PackageAppOpModes, MutablePackageAppOpModes>
 
@@ -388,7 +411,8 @@
     override fun toMutable(): MutableUserState = MutableUserState(this)
 }
 
-class MutableUserState private constructor(
+class MutableUserState
+private constructor(
     packageVersionsReference: PackageVersionsReference,
     appIdPermissionFlagsReference: AppIdPermissionFlagsReference,
     appIdDevicePermissionFlagsReference: AppIdDevicePermissionFlagsReference,
@@ -396,26 +420,31 @@
     packageAppOpModesReference: PackageAppOpModesReference,
     defaultPermissionGrantFingerprint: String?,
     writeMode: Int
-) : UserState(
-    packageVersionsReference,
-    appIdPermissionFlagsReference,
-    appIdDevicePermissionFlagsReference,
-    appIdAppOpModesReference,
-    packageAppOpModesReference,
-    defaultPermissionGrantFingerprint,
-    writeMode
-), MutableWritableState {
-    constructor() : this(
-        PackageVersionsReference(MutableIndexedMap<String, Int>()),
-        AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()),
-        AppIdDevicePermissionFlagsReference(MutableAppIdDevicePermissionFlags()),
-        AppIdAppOpModesReference(MutableAppIdAppOpModes()),
-        PackageAppOpModesReference(MutablePackageAppOpModes()),
-        null,
-        WriteMode.NONE
-    )
+) :
+    UserState(
+        packageVersionsReference,
+        appIdPermissionFlagsReference,
+        appIdDevicePermissionFlagsReference,
+        appIdAppOpModesReference,
+        packageAppOpModesReference,
+        defaultPermissionGrantFingerprint,
+        writeMode
+    ),
+    MutableWritableState {
+    constructor() :
+        this(
+            PackageVersionsReference(MutableIndexedMap<String, Int>()),
+            AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()),
+            AppIdDevicePermissionFlagsReference(MutableAppIdDevicePermissionFlags()),
+            AppIdAppOpModesReference(MutableAppIdAppOpModes()),
+            PackageAppOpModesReference(MutablePackageAppOpModes()),
+            null,
+            WriteMode.NONE
+        )
 
-    internal constructor(userState: UserState) : this(
+    internal constructor(
+        userState: UserState
+    ) : this(
         userState.packageVersionsReference.toImmutable(),
         userState.appIdPermissionFlagsReference.toImmutable(),
         userState.appIdDevicePermissionFlagsReference.toImmutable(),
@@ -461,11 +490,7 @@
     fun requestWriteMode(writeMode: Int)
 }
 
-open class GetStateScope(
-    val state: AccessState
-)
+open class GetStateScope(val state: AccessState)
 
-class MutateStateScope(
-    val oldState: AccessState,
-    val newState: MutableAccessState
-) : GetStateScope(newState)
+class MutateStateScope(val oldState: AccessState, val newState: MutableAccessState) :
+    GetStateScope(newState)
diff --git a/services/permission/java/com/android/server/permission/access/AccessUri.kt b/services/permission/java/com/android/server/permission/access/AccessUri.kt
index 1d46ca7..1f5a4df 100644
--- a/services/permission/java/com/android/server/permission/access/AccessUri.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessUri.kt
@@ -18,9 +18,7 @@
 
 import android.os.UserHandle
 
-sealed class AccessUri(
-    val scheme: String
-) {
+sealed class AccessUri(val scheme: String) {
     override fun equals(other: Any?): Boolean {
         throw NotImplementedError()
     }
@@ -34,9 +32,7 @@
     }
 }
 
-data class AppOpUri(
-    val appOpName: String
-) : AccessUri(SCHEME) {
+data class AppOpUri(val appOpName: String) : AccessUri(SCHEME) {
     override fun toString(): String = "$scheme:///$appOpName"
 
     companion object {
@@ -44,10 +40,7 @@
     }
 }
 
-data class PackageUri(
-    val packageName: String,
-    val userId: Int
-) : AccessUri(SCHEME) {
+data class PackageUri(val packageName: String, val userId: Int) : AccessUri(SCHEME) {
     override fun toString(): String = "$scheme:///$packageName/$userId"
 
     companion object {
@@ -55,9 +48,7 @@
     }
 }
 
-data class PermissionUri(
-    val permissionName: String
-) : AccessUri(SCHEME) {
+data class PermissionUri(val permissionName: String) : AccessUri(SCHEME) {
     override fun toString(): String = "$scheme:///$permissionName"
 
     companion object {
@@ -65,10 +56,7 @@
     }
 }
 
-data class DevicePermissionUri(
-    val permissionName: String,
-    val deviceId: Int
-) : AccessUri(SCHEME) {
+data class DevicePermissionUri(val permissionName: String, val deviceId: Int) : AccessUri(SCHEME) {
     override fun toString(): String = "$scheme:///$permissionName/$deviceId"
 
     companion object {
@@ -76,9 +64,7 @@
     }
 }
 
-data class UidUri(
-    val uid: Int
-) : AccessUri(SCHEME) {
+data class UidUri(val uid: Int) : AccessUri(SCHEME) {
     val userId: Int
         get() = UserHandle.getUserId(uid)
 
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt
index 96d315e..6c1b080 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt
@@ -46,9 +46,7 @@
 
             val appOpModes = MutableIndexedMap<String, Int>()
             appIdAppOpModes[appId] = appOpModes
-            legacyAppOpModes.forEach { (appOpName, appOpMode) ->
-                appOpModes[appOpName] = appOpMode
-            }
+            legacyAppOpModes.forEach { (appOpName, appOpMode) -> appOpModes[appOpName] = appOpMode }
 
             if (packageNames != null) {
                 val packageVersions = userState.mutatePackageVersions()
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt
index 4c7e946..f291b1a 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt
@@ -51,8 +51,10 @@
         }
         userState.appIdAppOpModes.forEachReversedIndexed { appIdIndex, appId, _ ->
             // Non-application UIDs may not have an Android package but may still have app op state.
-            if (appId !in state.externalState.appIdPackageNames &&
-                appId >= Process.FIRST_APPLICATION_UID) {
+            if (
+                appId !in state.externalState.appIdPackageNames &&
+                    appId >= Process.FIRST_APPLICATION_UID
+            ) {
                 Slog.w(LOG_TAG, "Dropping unknown app ID $appId when parsing app-op state")
                 appIdAppOpModes.removeAt(appIdIndex)
                 userState.requestWriteMode(WriteMode.ASYNCHRONOUS)
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
index c02fe4d..94caf28 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
@@ -46,7 +46,9 @@
         newState.userStates.forEachIndexed { userStateIndex, _, userState ->
             val appIdIndex = userState.appIdAppOpModes.indexOfKey(appId)
             if (appIdIndex >= 0) {
-                newState.mutateUserStateAt(userStateIndex).mutateAppIdAppOpModes()
+                newState
+                    .mutateUserStateAt(userStateIndex)
+                    .mutateAppIdAppOpModes()
                     .removeAt(appIdIndex)
                 // Skip notifying the change listeners since the app ID no longer exists.
             }
@@ -61,8 +63,8 @@
         if (userStateIndex < 0) {
             return false
         }
-        val appIdIndex = newState.userStates.valueAt(userStateIndex).appIdAppOpModes
-            .indexOfKey(appId)
+        val appIdIndex =
+            newState.userStates.valueAt(userStateIndex).appIdAppOpModes.indexOfKey(appId)
         if (appIdIndex < 0) {
             return false
         }
@@ -71,7 +73,9 @@
     }
 
     fun GetStateScope.getAppOpMode(appId: Int, userId: Int, appOpName: String): Int =
-        state.userStates[userId]?.appIdAppOpModes?.get(appId)
+        state.userStates[userId]
+            ?.appIdAppOpModes
+            ?.get(appId)
             .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName))
 
     fun MutateStateScope.setAppOpMode(
@@ -81,8 +85,10 @@
         mode: Int
     ): Boolean {
         val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
-        val oldMode = newState.userStates[userId]!!.appIdAppOpModes[appId]
-            .getWithDefault(appOpName, defaultMode)
+        val oldMode =
+            newState.userStates[userId]!!
+                .appIdAppOpModes[appId]
+                .getWithDefault(appOpName, defaultMode)
         if (oldMode == mode) {
             return false
         }
@@ -122,9 +128,7 @@
         with(upgrade) { upgradePackageState(packageState, userId, version) }
     }
 
-    /**
-     * Listener for app op mode changes.
-     */
+    /** Listener for app op mode changes. */
     abstract class OnAppOpModeChangedListener {
         /**
          * Called when an app op mode change has been made to the upcoming new state.
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt
index 12df95e..10c7764 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt
@@ -28,11 +28,13 @@
     ) {
         if (version <= 2) {
             with(policy) {
-                val appOpMode = getAppOpMode(
-                    packageState.appId, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND
-                )
+                val appOpMode =
+                    getAppOpMode(packageState.appId, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND)
                 setAppOpMode(
-                    packageState.appId, userId, AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, appOpMode
+                    packageState.appId,
+                    userId,
+                    AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND,
+                    appOpMode
                 )
             }
         }
@@ -40,14 +42,19 @@
             val permissionName = AppOpsManager.opToPermission(AppOpsManager.OP_SCHEDULE_EXACT_ALARM)
             if (permissionName in packageState.androidPackage!!.requestedPermissions) {
                 with(policy) {
-                    val appOpMode = getAppOpMode(
-                        packageState.appId, userId, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM
-                    )
+                    val appOpMode =
+                        getAppOpMode(
+                            packageState.appId,
+                            userId,
+                            AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM
+                        )
                     val defaultAppOpMode =
                         AppOpsManager.opToDefaultMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM)
                     if (appOpMode == defaultAppOpMode) {
                         setAppOpMode(
-                            packageState.appId, userId, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM,
+                            packageState.appId,
+                            userId,
+                            AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM,
                             AppOpsManager.MODE_ALLOWED
                         )
                     }
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index 5b91ad9..26ea9d2 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -33,19 +33,16 @@
 import com.android.server.permission.access.collection.forEachIndexed
 import com.android.server.permission.access.collection.set
 
-class AppOpService(
-    private val service: AccessCheckingService
-) : AppOpsCheckingServiceInterface {
-    private val packagePolicy = service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME)
-        as PackageAppOpPolicy
-    private val appIdPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME)
-        as AppIdAppOpPolicy
+class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingServiceInterface {
+    private val packagePolicy =
+        service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) as PackageAppOpPolicy
+    private val appIdPolicy =
+        service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy
 
     private val context = service.context
     private lateinit var handler: Handler
 
-    @Volatile
-    private var listeners = ArraySet<AppOpsModeChangedListener>()
+    @Volatile private var listeners = ArraySet<AppOpsModeChangedListener>()
     private val listenersLock = Any()
 
     fun initialize() {
@@ -86,9 +83,7 @@
         val appId = UserHandle.getAppId(uid)
         val userId = UserHandle.getUserId(uid)
         val opName = AppOpsManager.opToPublicName(op)
-        return service.getState {
-            with(appIdPolicy) { getAppOpMode(appId, userId, opName) }
-        }
+        return service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } }
     }
 
     private fun getUidModes(uid: Int): ArrayMap<String, Int>? {
@@ -115,10 +110,7 @@
         }
     }
 
-    private fun getPackageModes(
-        packageName: String,
-        userId: Int
-    ): ArrayMap<String, Int>? =
+    private fun getPackageModes(packageName: String, userId: Int): ArrayMap<String, Int>? =
         service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }?.map
 
     override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) {
@@ -131,15 +123,13 @@
     override fun removeUid(uid: Int) {
         val appId = UserHandle.getAppId(uid)
         val userId = UserHandle.getUserId(uid)
-        service.mutateState {
-            with(appIdPolicy) { removeAppOpModes(appId, userId) }
-        }
+        service.mutateState { with(appIdPolicy) { removeAppOpModes(appId, userId) } }
     }
 
     override fun removePackage(packageName: String, userId: Int): Boolean {
         var wasChanged = false
         service.mutateState {
-            wasChanged = with (packagePolicy) { removeAppOpModes(packageName, userId) }
+            wasChanged = with(packagePolicy) { removeAppOpModes(packageName, userId) }
         }
         return wasChanged
     }
diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt
index a267637..edeef71 100644
--- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt
@@ -52,9 +52,7 @@
     }
 
     protected fun BinaryXmlSerializer.serializeAppOps(appOpModes: IndexedMap<String, Int>) {
-        appOpModes.forEachIndexed { _, name, mode ->
-            serializeAppOp(name, mode)
-        }
+        appOpModes.forEachIndexed { _, name, mode -> serializeAppOp(name, mode) }
     }
 
     private fun BinaryXmlSerializer.serializeAppOp(name: String, mode: Int) {
diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
index c0a85f8..758cec0 100644
--- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
@@ -23,9 +23,7 @@
 import com.android.server.permission.access.MutableAccessState
 import com.android.server.permission.access.SchemePolicy
 
-abstract class BaseAppOpPolicy(
-    private val persistence: BaseAppOpPersistence
-) : SchemePolicy() {
+abstract class BaseAppOpPolicy(private val persistence: BaseAppOpPersistence) : SchemePolicy() {
     override val objectScheme: String
         get() = AppOpUri.SCHEME
 
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt
index 03311a2..8797e39 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt
@@ -44,9 +44,7 @@
 
             val appOpModes = MutableIndexedMap<String, Int>()
             packageAppOpModes[packageName] = appOpModes
-            legacyAppOpModes.forEach { (appOpName, appOpMode) ->
-                appOpModes[appOpName] = appOpMode
-            }
+            legacyAppOpModes.forEach { (appOpName, appOpMode) -> appOpModes[appOpName] = appOpMode }
 
             userState.mutatePackageVersions()[packageName] = version
         }
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index 5398a57..0d9470e 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -46,7 +46,9 @@
         newState.userStates.forEachIndexed { userStateIndex, _, userState ->
             val packageNameIndex = userState.packageAppOpModes.indexOfKey(packageName)
             if (packageNameIndex >= 0) {
-                newState.mutateUserStateAt(userStateIndex).mutatePackageAppOpModes()
+                newState
+                    .mutateUserStateAt(userStateIndex)
+                    .mutatePackageAppOpModes()
                     .removeAt(packageNameIndex)
                 // Skip notifying the change listeners since the package no longer exists.
             }
@@ -61,18 +63,22 @@
         if (userStateIndex < 0) {
             return false
         }
-        val packageNameIndex = newState.userStates.valueAt(userStateIndex).packageAppOpModes
-            .indexOfKey(packageName)
+        val packageNameIndex =
+            newState.userStates.valueAt(userStateIndex).packageAppOpModes.indexOfKey(packageName)
         if (packageNameIndex < 0) {
             return false
         }
-        newState.mutateUserStateAt(userStateIndex).mutatePackageAppOpModes()
+        newState
+            .mutateUserStateAt(userStateIndex)
+            .mutatePackageAppOpModes()
             .removeAt(packageNameIndex)
         return true
     }
 
     fun GetStateScope.getAppOpMode(packageName: String, userId: Int, appOpName: String): Int =
-        state.userStates[userId]?.packageAppOpModes?.get(packageName)
+        state.userStates[userId]
+            ?.packageAppOpModes
+            ?.get(packageName)
             .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName))
 
     fun MutateStateScope.setAppOpMode(
@@ -82,8 +88,10 @@
         mode: Int
     ): Boolean {
         val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
-        val oldMode = newState.userStates[userId]!!.packageAppOpModes[packageName]
-            .getWithDefault(appOpName, defaultMode)
+        val oldMode =
+            newState.userStates[userId]!!
+                .packageAppOpModes[packageName]
+                .getWithDefault(appOpName, defaultMode)
         if (oldMode == mode) {
             return false
         }
@@ -123,9 +131,7 @@
         with(upgrade) { upgradePackageState(packageState, userId, version) }
     }
 
-    /**
-     * Listener for app op mode changes.
-     */
+    /** Listener for app op mode changes. */
     abstract class OnAppOpModeChangedListener {
         /**
          * Called when an app op mode change has been made to the upcoming new state.
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt
index 8e37093..f5eedf7 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt
@@ -28,11 +28,16 @@
     ) {
         if (version <= 2) {
             with(policy) {
-                val appOpMode = getAppOpMode(
-                    packageState.packageName, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND
-                )
+                val appOpMode =
+                    getAppOpMode(
+                        packageState.packageName,
+                        userId,
+                        AppOpsManager.OPSTR_RUN_IN_BACKGROUND
+                    )
                 setAppOpMode(
-                    packageState.packageName, userId, AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND,
+                    packageState.packageName,
+                    userId,
+                    AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND,
                     appOpMode
                 )
             }
diff --git a/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt
index 686db42..b74f477 100644
--- a/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt
@@ -49,7 +49,9 @@
 }
 
 inline fun <K, V> ArrayMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V {
-    get(key)?.let { return it }
+    get(key)?.let {
+        return it
+    }
     return defaultValue().also { put(key, it) }
 }
 
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt
index ce4aa44..ea8e07f 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt
@@ -16,12 +16,8 @@
 
 package com.android.server.permission.access.immutable
 
-/**
- * Immutable list with index-based access.
- */
-sealed class IndexedList<T>(
-    internal val list: ArrayList<T>
-) : Immutable<MutableIndexedList<T>> {
+/** Immutable list with index-based access. */
+sealed class IndexedList<T>(internal val list: ArrayList<T>) : Immutable<MutableIndexedList<T>> {
     val size: Int
         get() = list.size
 
@@ -29,20 +25,15 @@
 
     operator fun contains(element: T): Boolean = list.contains(element)
 
-    @Suppress("ReplaceGetOrSet")
-    operator fun get(index: Int): T = list.get(index)
+    @Suppress("ReplaceGetOrSet") operator fun get(index: Int): T = list.get(index)
 
     override fun toMutable(): MutableIndexedList<T> = MutableIndexedList(this)
 
     override fun toString(): String = list.toString()
 }
 
-/**
- * Mutable list with index-based access.
- */
-class MutableIndexedList<T>(
-    list: ArrayList<T> = ArrayList()
-) : IndexedList<T>(list) {
+/** Mutable list with index-based access. */
+class MutableIndexedList<T>(list: ArrayList<T> = ArrayList()) : IndexedList<T>(list) {
     constructor(indexedList: IndexedList<T>) : this(ArrayList(indexedList.list))
 
     @Suppress("ReplaceGetOrSet")
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt
index dc9bae3..a9d804e 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt
@@ -70,9 +70,7 @@
     accumulator: (Int, Int, T) -> Int
 ): Int {
     var value = initialValue
-    forEachIndexed { index, element ->
-        value = accumulator(value, index, element)
-    }
+    forEachIndexed { index, element -> value = accumulator(value, index, element) }
     return value
 }
 
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt
index 77e71ba..3a2fd2f 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt
@@ -16,12 +16,9 @@
 
 package com.android.server.permission.access.immutable
 
-/**
- * Immutable set with index-based access, implemented using a list.
- */
-sealed class IndexedListSet<T>(
-    internal val list: ArrayList<T>
-) : Immutable<MutableIndexedListSet<T>> {
+/** Immutable set with index-based access, implemented using a list. */
+sealed class IndexedListSet<T>(internal val list: ArrayList<T>) :
+    Immutable<MutableIndexedListSet<T>> {
     val size: Int
         get() = list.size
 
@@ -31,20 +28,15 @@
 
     fun indexOf(element: T): Int = list.indexOf(element)
 
-    @Suppress("ReplaceGetOrSet")
-    fun elementAt(index: Int): T = list.get(index)
+    @Suppress("ReplaceGetOrSet") fun elementAt(index: Int): T = list.get(index)
 
     override fun toMutable(): MutableIndexedListSet<T> = MutableIndexedListSet(this)
 
     override fun toString(): String = list.toString()
 }
 
-/**
- * Mutable set with index-based access, implemented using a list.
- */
-class MutableIndexedListSet<T>(
-    list: ArrayList<T> = ArrayList()
-) : IndexedListSet<T>(list) {
+/** Mutable set with index-based access, implemented using a list. */
+class MutableIndexedListSet<T>(list: ArrayList<T> = ArrayList()) : IndexedListSet<T>(list) {
     constructor(indexedListSet: IndexedListSet<T>) : this(ArrayList(indexedListSet.list))
 
     fun add(element: T): Boolean =
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt
index 13fc141..2634b53 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt
@@ -70,9 +70,7 @@
     accumulator: (Int, Int, T) -> Int
 ): Int {
     var value = initialValue
-    forEachIndexed { index, element ->
-        value = accumulator(value, index, element)
-    }
+    forEachIndexed { index, element -> value = accumulator(value, index, element) }
     return value
 }
 
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt
index 299cc89..873c9c8 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt
@@ -18,12 +18,9 @@
 
 import android.util.ArrayMap
 
-/**
- * Immutable map with index-based access.
- */
-sealed class IndexedMap<K, V>(
-    internal val map: ArrayMap<K, V>
-) : Immutable<MutableIndexedMap<K, V>> {
+/** Immutable map with index-based access. */
+sealed class IndexedMap<K, V>(internal val map: ArrayMap<K, V>) :
+    Immutable<MutableIndexedMap<K, V>> {
     val size: Int
         get() = map.size
 
@@ -31,8 +28,7 @@
 
     operator fun contains(key: K): Boolean = map.containsKey(key)
 
-    @Suppress("ReplaceGetOrSet")
-    operator fun get(key: K): V? = map.get(key)
+    @Suppress("ReplaceGetOrSet") operator fun get(key: K): V? = map.get(key)
 
     fun indexOfKey(key: K): Int = map.indexOfKey(key)
 
@@ -45,12 +41,8 @@
     override fun toString(): String = map.toString()
 }
 
-/**
- * Mutable map with index-based access.
- */
-class MutableIndexedMap<K, V>(
-    map: ArrayMap<K, V> = ArrayMap()
-) : IndexedMap<K, V>(map) {
+/** Mutable map with index-based access. */
+class MutableIndexedMap<K, V>(map: ArrayMap<K, V> = ArrayMap()) : IndexedMap<K, V>(map) {
     constructor(indexedMap: IndexedMap<K, V>) : this(ArrayMap(indexedMap.map))
 
     fun put(key: K, value: V): V? = map.put(key, value)
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt
index 69f1779c..48637cc 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt
@@ -36,7 +36,9 @@
 
 inline fun <K, V, R> IndexedMap<K, V>.firstNotNullOfOrNullIndexed(transform: (Int, K, V) -> R): R? {
     forEachIndexed { index, key, value ->
-        transform(index, key, value)?.let { return it }
+        transform(index, key, value)?.let {
+            return it
+        }
     }
     return null
 }
@@ -75,9 +77,7 @@
     destination: C,
     transform: (Int, K, V) -> R,
 ): C {
-    forEachIndexed { index, key, value ->
-        transform(index, key, value).let { destination += it }
-    }
+    forEachIndexed { index, key, value -> transform(index, key, value).let { destination += it } }
     return destination
 }
 
@@ -85,14 +85,14 @@
     destination: C,
     transform: (Int, K, V) -> R?
 ): C {
-    forEachIndexed { index, key, value ->
-        transform(index, key, value)?.let { destination += it }
-    }
+    forEachIndexed { index, key, value -> transform(index, key, value)?.let { destination += it } }
     return destination
 }
 
 inline fun <K, V> MutableIndexedMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V {
-    get(key)?.let { return it }
+    get(key)?.let {
+        return it
+    }
     return defaultValue().also { put(key, it) }
 }
 
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
index ff76a47..6fe4718 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
@@ -33,8 +33,7 @@
 
     operator fun contains(key: K): Boolean = map.containsKey(key)
 
-    @Suppress("ReplaceGetOrSet")
-    operator fun get(key: K): I? = map.get(key)?.get()
+    @Suppress("ReplaceGetOrSet") operator fun get(key: K): I? = map.get(key)?.get()
 
     fun indexOfKey(key: K): Int = map.indexOfKey(key)
 
@@ -55,7 +54,9 @@
 class MutableIndexedReferenceMap<K, I : Immutable<M>, M : I>(
     map: ArrayMap<K, MutableReference<I, M>> = ArrayMap()
 ) : IndexedReferenceMap<K, I, M>(map) {
-    constructor(indexedReferenceMap: IndexedReferenceMap<K, I, M>) : this(
+    constructor(
+        indexedReferenceMap: IndexedReferenceMap<K, I, M>
+    ) : this(
         ArrayMap(indexedReferenceMap.map).apply {
             for (i in 0 until size) {
                 setValueAt(i, valueAt(i).toImmutable())
@@ -63,8 +64,7 @@
         }
     )
 
-    @Suppress("ReplaceGetOrSet")
-    fun mutate(key: K): M? = map.get(key)?.mutate()
+    @Suppress("ReplaceGetOrSet") fun mutate(key: K): M? = map.get(key)?.mutate()
 
     fun put(key: K, value: M): I? = map.put(key, MutableReference(value))?.get()
 
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt
index 22b4d52..43a902b 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt
@@ -72,7 +72,9 @@
     key: K,
     defaultValue: () -> M
 ): M {
-    mutate(key)?.let { return it }
+    mutate(key)?.let {
+        return it
+    }
     return defaultValue().also { put(key, it) }
 }
 
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
index 547e56c..cbc24b1 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
@@ -18,12 +18,8 @@
 
 import android.util.ArraySet
 
-/**
- * Immutable set with index-based access.
- */
-sealed class IndexedSet<T>(
-    internal val set: ArraySet<T>
-) : Immutable<MutableIndexedSet<T>> {
+/** Immutable set with index-based access. */
+sealed class IndexedSet<T>(internal val set: ArraySet<T>) : Immutable<MutableIndexedSet<T>> {
     val size: Int
         get() = set.size
 
@@ -40,12 +36,8 @@
     override fun toString(): String = set.toString()
 }
 
-/**
- * Mutable set with index-based access.
- */
-class MutableIndexedSet<T>(
-    set: ArraySet<T> = ArraySet()
-) : IndexedSet<T>(set) {
+/** Mutable set with index-based access. */
+class MutableIndexedSet<T>(set: ArraySet<T> = ArraySet()) : IndexedSet<T>(set) {
     constructor(indexedSet: IndexedSet<T>) : this(ArraySet(indexedSet.set))
 
     fun add(element: T): Boolean = set.add(element)
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt
index 7ed29e8..e9a405f 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt
@@ -18,12 +18,8 @@
 
 import android.util.SparseArray
 
-/**
- * Immutable map with index-based access and [Int] keys.
- */
-sealed class IntMap<T>(
-    internal val array: SparseArray<T>
-) : Immutable<MutableIntMap<T>> {
+/** Immutable map with index-based access and [Int] keys. */
+sealed class IntMap<T>(internal val array: SparseArray<T>) : Immutable<MutableIntMap<T>> {
     val size: Int
         get() = array.size()
 
@@ -44,12 +40,8 @@
     override fun toString(): String = array.toString()
 }
 
-/**
- * Mutable map with index-based access and [Int] keys.
- */
-class MutableIntMap<T>(
-    array: SparseArray<T> = SparseArray()
-) : IntMap<T>(array) {
+/** Mutable map with index-based access and [Int] keys. */
+class MutableIntMap<T>(array: SparseArray<T> = SparseArray()) : IntMap<T>(array) {
     constructor(intMap: IntMap<T>) : this(intMap.array.clone())
 
     fun put(key: Int, value: T): T? = array.putReturnOld(key, value)
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt
index 9aa0a41..09d7319 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt
@@ -36,7 +36,9 @@
 
 inline fun <T, R> IntMap<T>.firstNotNullOfOrNullIndexed(transform: (Int, Int, T) -> R): R? {
     forEachIndexed { index, key, value ->
-        transform(index, key, value)?.let { return it }
+        transform(index, key, value)?.let {
+            return it
+        }
     }
     return null
 }
@@ -72,7 +74,9 @@
 }
 
 inline fun <T> MutableIntMap<T>.getOrPut(key: Int, defaultValue: () -> T): T {
-    get(key)?.let { return it }
+    get(key)?.let {
+        return it
+    }
     return defaultValue().also { put(key, it) }
 }
 
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt
index 160b227..3f26517 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt
@@ -33,8 +33,7 @@
 
     operator fun contains(key: Int): Boolean = array.contains(key)
 
-    @Suppress("ReplaceGetOrSet")
-    operator fun get(key: Int): I? = array.get(key)?.get()
+    @Suppress("ReplaceGetOrSet") operator fun get(key: Int): I? = array.get(key)?.get()
 
     fun indexOfKey(key: Int): Int = array.indexOfKey(key)
 
@@ -55,7 +54,9 @@
 class MutableIntReferenceMap<I : Immutable<M>, M : I>(
     array: SparseArray<MutableReference<I, M>> = SparseArray()
 ) : IntReferenceMap<I, M>(array) {
-    constructor(intReferenceMap: IntReferenceMap<I, M>) : this(
+    constructor(
+        intReferenceMap: IntReferenceMap<I, M>
+    ) : this(
         intReferenceMap.array.clone().apply {
             for (i in 0 until size()) {
                 setValueAt(i, valueAt(i).toImmutable())
@@ -63,8 +64,7 @@
         }
     )
 
-    @Suppress("ReplaceGetOrSet")
-    fun mutate(key: Int): M? = array.get(key)?.mutate()
+    @Suppress("ReplaceGetOrSet") fun mutate(key: Int): M? = array.get(key)?.mutate()
 
     fun put(key: Int, value: M): I? = array.putReturnOld(key, MutableReference(value))?.get()
 
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt
index 1ed4f8a..a1bab95 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt
@@ -72,7 +72,9 @@
     key: Int,
     defaultValue: () -> M
 ): M {
-    mutate(key)?.let { return it }
+    mutate(key)?.let {
+        return it
+    }
     return defaultValue().also { put(key, it) }
 }
 
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt
index 21f2af2..1254797 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt
@@ -18,12 +18,8 @@
 
 import android.util.SparseBooleanArray
 
-/**
- * Immutable set with index-based access and [Int] elements.
- */
-sealed class IntSet(
-    internal val array: SparseBooleanArray
-) : Immutable<MutableIntSet> {
+/** Immutable set with index-based access and [Int] elements. */
+sealed class IntSet(internal val array: SparseBooleanArray) : Immutable<MutableIntSet> {
     val size: Int
         get() = array.size()
 
@@ -40,12 +36,8 @@
     override fun toString(): String = array.toString()
 }
 
-/**
- * Mutable set with index-based access and [Int] elements.
- */
-class MutableIntSet(
-    array: SparseBooleanArray = SparseBooleanArray()
-) : IntSet(array) {
+/** Mutable set with index-based access and [Int] elements. */
+class MutableIntSet(array: SparseBooleanArray = SparseBooleanArray()) : IntSet(array) {
     constructor(intSet: IntSet) : this(intSet.array.clone())
 
     fun add(element: Int): Boolean =
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt
index 163ebbf..9d0d14f 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt
@@ -66,7 +66,7 @@
 
 operator fun IntSet.plus(element: Int): MutableIntSet = toMutable().apply { this += element }
 
-fun MutableIntSet(values: IntArray): MutableIntSet = MutableIntSet().apply{ this += values }
+fun MutableIntSet(values: IntArray): MutableIntSet = MutableIntSet().apply { this += values }
 
 operator fun MutableIntSet.plusAssign(element: Int) {
     array.put(element, true)
diff --git a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
index 171cfeb..471a71b 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
@@ -27,21 +27,17 @@
  * exposed on the immutable interface of the data structure as a `getFoo` method, and the [mutate]
  * method exposed on the mutable interface of the data structure as a `mutateFoo` method. When the
  * data structure is mutated/copied, a new instance of this class should be obtained with
- * [toImmutable], which makes the wrapped reference immutable-only again and thus prevents
- * further modifications to a data structure accessed with its immutable interface.
+ * [toImmutable], which makes the wrapped reference immutable-only again and thus prevents further
+ * modifications to a data structure accessed with its immutable interface.
  *
  * @see MutableIndexedReferenceMap
  * @see MutableIntReferenceMap
  */
-class MutableReference<I : Immutable<M>, M : I> private constructor(
-    private var immutable: I,
-    private var mutable: M?
-) {
+class MutableReference<I : Immutable<M>, M : I>
+private constructor(private var immutable: I, private var mutable: M?) {
     constructor(mutable: M) : this(mutable, mutable)
 
-    /**
-     * Return an immutable reference to the wrapped mutable data structure.
-     */
+    /** Return an immutable reference to the wrapped mutable data structure. */
     fun get(): I = immutable
 
     /**
@@ -50,7 +46,9 @@
      * already mutable.
      */
     fun mutate(): M {
-        mutable?.let { return it }
+        mutable?.let {
+            return it
+        }
         return immutable.toMutable().also {
             immutable = it
             mutable = it
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt
index 691ed8f..2983895 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt
@@ -23,9 +23,7 @@
 import com.android.server.permission.access.util.PackageVersionMigration
 import com.android.server.pm.permission.PermissionMigrationHelper
 
-/**
- * This class migrate legacy permissions to unified permission subsystem
- */
+/** This class migrate legacy permissions to unified permission subsystem */
 class AppIdPermissionMigration {
     internal fun migrateSystemState(state: MutableAccessState) {
         val legacyPermissionsManager =
@@ -34,10 +32,15 @@
             return
         }
 
-        migratePermissions(state.mutateSystemState().mutatePermissions(),
-            legacyPermissionsManager.legacyPermissions)
-        migratePermissions(state.mutateSystemState().mutatePermissionTrees(),
-            legacyPermissionsManager.legacyPermissionTrees, true)
+        migratePermissions(
+            state.mutateSystemState().mutatePermissions(),
+            legacyPermissionsManager.legacyPermissions
+        )
+        migratePermissions(
+            state.mutateSystemState().mutatePermissionTrees(),
+            legacyPermissionsManager.legacyPermissionTrees,
+            true
+        )
     }
 
     private fun migratePermissions(
@@ -46,14 +49,15 @@
         isPermissionTree: Boolean = false
     ) {
         legacyPermissions.forEach { (_, legacyPermission) ->
-            val permission = Permission(
-                legacyPermission.permissionInfo, false, legacyPermission.type, 0
-            )
+            val permission =
+                Permission(legacyPermission.permissionInfo, false, legacyPermission.type, 0)
             permissions[permission.name] = permission
             if (DEBUG_MIGRATION) {
-                Slog.v(LOG_TAG, "Migrated permission: ${permission.name}, type: " +
-                    "${permission.type}, appId: ${permission.appId}, protectionLevel: " +
-                    "${permission.protectionLevel}, tree: $isPermissionTree"
+                Slog.v(
+                    LOG_TAG,
+                    "Migrated permission: ${permission.name}, type: " +
+                        "${permission.type}, appId: ${permission.appId}, protectionLevel: " +
+                        "${permission.protectionLevel}, tree: $isPermissionTree"
                 )
             }
         }
@@ -81,25 +85,23 @@
 
             val permissionFlags = MutableIndexedMap<String, Int>()
             appIdPermissionFlags[appId] = permissionFlags
-            legacyPermissionStates.forEach forEachPermission@ {
+            legacyPermissionStates.forEach forEachPermission@{
                 (permissionName, legacyPermissionState) ->
                 val permission = state.systemState.permissions[permissionName]
                 if (permission == null) {
                     Slog.w(
-                        LOG_TAG, "Dropping unknown permission $permissionName for app ID $appId" +
+                        LOG_TAG,
+                        "Dropping unknown permission $permissionName for app ID $appId" +
                             " when migrating permission state"
                     )
                     return@forEachPermission
                 }
-                permissionFlags[permissionName] = migratePermissionFlags(
-                    permission, legacyPermissionState, appId, userId
-                )
+                permissionFlags[permissionName] =
+                    migratePermissionFlags(permission, legacyPermissionState, appId, userId)
             }
 
             val packageVersions = userState.mutatePackageVersions()
-            packageNames.forEachIndexed { _, packageName ->
-                packageVersions[packageName] = version
-            }
+            packageNames.forEachIndexed { _, packageName -> packageVersions[packageName] = version }
         }
     }
 
@@ -109,29 +111,35 @@
         appId: Int,
         userId: Int
     ): Int {
-        var flags = when {
-            permission.isNormal -> if (legacyPermissionState.isGranted) {
-                PermissionFlags.INSTALL_GRANTED
-            } else {
-                PermissionFlags.INSTALL_REVOKED
-            }
-            permission.isSignature || permission.isInternal ->
-                if (legacyPermissionState.isGranted) {
-                    if (permission.isDevelopment || permission.isRole) {
-                        PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED
+        var flags =
+            when {
+                permission.isNormal ->
+                    if (legacyPermissionState.isGranted) {
+                        PermissionFlags.INSTALL_GRANTED
                     } else {
-                        PermissionFlags.PROTECTION_GRANTED
+                        PermissionFlags.INSTALL_REVOKED
                     }
-                } else {
-                    0
-                }
-            permission.isRuntime ->
-                if (legacyPermissionState.isGranted) PermissionFlags.RUNTIME_GRANTED else 0
-            else -> 0
-        }
-        flags = PermissionFlags.updateFlags(
-            permission, flags, legacyPermissionState.flags, legacyPermissionState.flags
-        )
+                permission.isSignature || permission.isInternal ->
+                    if (legacyPermissionState.isGranted) {
+                        if (permission.isDevelopment || permission.isRole) {
+                            PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED
+                        } else {
+                            PermissionFlags.PROTECTION_GRANTED
+                        }
+                    } else {
+                        0
+                    }
+                permission.isRuntime ->
+                    if (legacyPermissionState.isGranted) PermissionFlags.RUNTIME_GRANTED else 0
+                else -> 0
+            }
+        flags =
+            PermissionFlags.updateFlags(
+                permission,
+                flags,
+                legacyPermissionState.flags,
+                legacyPermissionState.flags
+            )
         if (DEBUG_MIGRATION) {
             val oldFlagString = PermissionFlags.apiFlagsToString(legacyPermissionState.flags)
             val newFlagString = PermissionFlags.toString(flags)
@@ -139,7 +147,8 @@
             val newGrantState = PermissionFlags.isPermissionGranted(flags)
             val flagsMismatch = legacyPermissionState.flags != PermissionFlags.toApiFlags(flags)
             Slog.v(
-                LOG_TAG, "Migrated appId: $appId, permission: " +
+                LOG_TAG,
+                "Migrated appId: $appId, permission: " +
                     "${permission.name}, user: $userId, oldGrantState: $oldGrantState" +
                     ", oldFlags: $oldFlagString, newFlags: $newFlagString, grantMismatch: " +
                     "${oldGrantState != newGrantState}, flagsMismatch: $flagsMismatch"
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt
index 2c8175b..1f40f01 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt
@@ -57,11 +57,12 @@
         isPermissionTree: Boolean
     ) {
         val systemState = state.mutateSystemState(WriteMode.NONE)
-        val permissions = if (isPermissionTree) {
-            systemState.mutatePermissionTrees()
-        } else {
-            systemState.mutatePermissions()
-        }
+        val permissions =
+            if (isPermissionTree) {
+                systemState.mutatePermissionTrees()
+            } else {
+                systemState.mutatePermissions()
+            }
         forEachTag {
             when (val tagName = tagName) {
                 TAG_PERMISSION -> parsePermission(permissions)
@@ -71,10 +72,13 @@
         permissions.forEachReversedIndexed { permissionIndex, _, permission ->
             val packageName = permission.packageName
             val externalState = state.externalState
-            if (packageName !in externalState.packageStates &&
-                packageName !in externalState.disabledSystemPackageStates) {
+            if (
+                packageName !in externalState.packageStates &&
+                    packageName !in externalState.disabledSystemPackageStates
+            ) {
                 Slog.w(
-                    LOG_TAG, "Dropping permission ${permission.name} from unknown package" +
+                    LOG_TAG,
+                    "Dropping permission ${permission.name} from unknown package" +
                         " $packageName when parsing permissions"
                 )
                 permissions.removeAt(permissionIndex)
@@ -88,11 +92,12 @@
     ) {
         val name = getAttributeValueOrThrow(ATTR_NAME).intern()
         @Suppress("DEPRECATION")
-        val permissionInfo = PermissionInfo().apply {
-            this.name = name
-            packageName = getAttributeValueOrThrow(ATTR_PACKAGE_NAME).intern()
-            protectionLevel = getAttributeIntHexOrThrow(ATTR_PROTECTION_LEVEL)
-        }
+        val permissionInfo =
+            PermissionInfo().apply {
+                this.name = name
+                packageName = getAttributeValueOrThrow(ATTR_PACKAGE_NAME).intern()
+                protectionLevel = getAttributeIntHexOrThrow(ATTR_PROTECTION_LEVEL)
+            }
         val type = getAttributeIntOrThrow(ATTR_TYPE)
         when (type) {
             Permission.TYPE_MANIFEST -> {}
@@ -125,15 +130,14 @@
         tagName: String,
         permissions: IndexedMap<String, Permission>
     ) {
-        tag(tagName) {
-            permissions.forEachIndexed { _, _, it -> serializePermission(it) }
-        }
+        tag(tagName) { permissions.forEachIndexed { _, _, it -> serializePermission(it) } }
     }
 
     private fun BinaryXmlSerializer.serializePermission(permission: Permission) {
         val type = permission.type
         when (type) {
-            Permission.TYPE_MANIFEST, Permission.TYPE_DYNAMIC -> {}
+            Permission.TYPE_MANIFEST,
+            Permission.TYPE_DYNAMIC -> {}
             Permission.TYPE_CONFIG -> return
             else -> {
                 Slog.w(LOG_TAG, "Skipping serializing permission $name with unknown type $type")
@@ -228,11 +232,12 @@
         tag(TAG_PERMISSION) {
             attributeInterned(ATTR_NAME, name)
             // Never serialize one-time permissions as granted.
-            val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) {
-                flags andInv PermissionFlags.RUNTIME_GRANTED
-            } else {
-                flags
-            }
+            val serializedFlags =
+                if (flags.hasBits(PermissionFlags.ONE_TIME)) {
+                    flags andInv PermissionFlags.RUNTIME_GRANTED
+                } else {
+                    flags
+                }
             attributeInt(ATTR_FLAGS, serializedFlags)
         }
     }
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 345f101..08ba753 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
@@ -55,7 +55,8 @@
 
     @Volatile
     private var onPermissionFlagsChangedListeners:
-        IndexedListSet<OnPermissionFlagsChangedListener> = MutableIndexedListSet()
+        IndexedListSet<OnPermissionFlagsChangedListener> =
+        MutableIndexedListSet()
     private val onPermissionFlagsChangedListenersLock = Any()
 
     private val privilegedPermissionAllowlistViolations = MutableIndexedSet<String>()
@@ -73,30 +74,37 @@
     override fun MutateStateScope.onInitialized() {
         newState.externalState.configPermissions.forEach { (permissionName, permissionEntry) ->
             val oldPermission = newState.systemState.permissions[permissionName]
-            val newPermission = if (oldPermission != null) {
-                if (permissionEntry.gids != null) {
-                    oldPermission.copy(
-                        gids = permissionEntry.gids, areGidsPerUser = permissionEntry.perUser
-                    )
+            val newPermission =
+                if (oldPermission != null) {
+                    if (permissionEntry.gids != null) {
+                        oldPermission.copy(
+                            gids = permissionEntry.gids,
+                            areGidsPerUser = permissionEntry.perUser
+                        )
+                    } else {
+                        return@forEach
+                    }
                 } else {
-                    return@forEach
+                    @Suppress("DEPRECATION")
+                    val permissionInfo =
+                        PermissionInfo().apply {
+                            name = permissionName
+                            packageName = PLATFORM_PACKAGE_NAME
+                            protectionLevel = PermissionInfo.PROTECTION_SIGNATURE
+                        }
+                    if (permissionEntry.gids != null) {
+                        Permission(
+                            permissionInfo,
+                            false,
+                            Permission.TYPE_CONFIG,
+                            0,
+                            permissionEntry.gids,
+                            permissionEntry.perUser
+                        )
+                    } else {
+                        Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0)
+                    }
                 }
-            } else {
-                @Suppress("DEPRECATION")
-                val permissionInfo = PermissionInfo().apply {
-                    name = permissionName
-                    packageName = PLATFORM_PACKAGE_NAME
-                    protectionLevel = PermissionInfo.PROTECTION_SIGNATURE
-                }
-                if (permissionEntry.gids != null) {
-                    Permission(
-                        permissionInfo, false, Permission.TYPE_CONFIG, 0, permissionEntry.gids,
-                        permissionEntry.perUser
-                    )
-                } else {
-                    Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0)
-                }
-            }
             newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission
         }
     }
@@ -200,30 +208,32 @@
         val androidPackage = packageState.androidPackage ?: return
         val appId = packageState.appId
         androidPackage.requestedPermissions.forEach { permissionName ->
-            val permission = newState.systemState.permissions[permissionName]
-                ?: return@forEach
+            val permission = newState.systemState.permissions[permissionName] ?: return@forEach
             if (!permission.isHardOrSoftRestricted) {
                 return@forEach
             }
-            val isRequestedBySystemPackage = anyPackageInAppId(appId) {
-                it.isSystem && permissionName in it.androidPackage!!.requestedPermissions
-            }
+            val isRequestedBySystemPackage =
+                anyPackageInAppId(appId) {
+                    it.isSystem && permissionName in it.androidPackage!!.requestedPermissions
+                }
             if (isRequestedBySystemPackage) {
                 return@forEach
             }
             val oldFlags = getPermissionFlags(appId, userId, permissionName)
             var newFlags = oldFlags andInv PermissionFlags.UPGRADE_EXEMPT
             val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
-            newFlags = if (permission.isHardRestricted && !isExempt) {
-                newFlags or PermissionFlags.RESTRICTION_REVOKED
-            } else {
-                newFlags andInv PermissionFlags.RESTRICTION_REVOKED
-            }
-            newFlags = if (permission.isSoftRestricted && !isExempt) {
-                newFlags or PermissionFlags.SOFT_RESTRICTED
-            } else {
-                newFlags andInv PermissionFlags.SOFT_RESTRICTED
-            }
+            newFlags =
+                if (permission.isHardRestricted && !isExempt) {
+                    newFlags or PermissionFlags.RESTRICTION_REVOKED
+                } else {
+                    newFlags andInv PermissionFlags.RESTRICTION_REVOKED
+                }
+            newFlags =
+                if (permission.isSoftRestricted && !isExempt) {
+                    newFlags or PermissionFlags.SOFT_RESTRICTED
+                } else {
+                    newFlags andInv PermissionFlags.SOFT_RESTRICTED
+                }
             setPermissionFlags(appId, userId, permissionName, newFlags)
         }
     }
@@ -243,15 +253,15 @@
         val androidPackage = packageState.androidPackage ?: return
         val appId = packageState.appId
         androidPackage.requestedPermissions.forEach { permissionName ->
-            val permission = newState.systemState.permissions[permissionName]
-                ?: return@forEach
+            val permission = newState.systemState.permissions[permissionName] ?: return@forEach
             if (!permission.isRuntime || permission.isRemoved) {
                 return@forEach
             }
-            val isRequestedByOtherPackages = anyPackageInAppId(appId) {
-                it.packageName != packageName &&
-                    permissionName in it.androidPackage!!.requestedPermissions
-            }
+            val isRequestedByOtherPackages =
+                anyPackageInAppId(appId) {
+                    it.packageName != packageName &&
+                        permissionName in it.androidPackage!!.requestedPermissions
+                }
             if (isRequestedByOtherPackages) {
                 return@forEach
             }
@@ -260,13 +270,15 @@
                 return@forEach
             }
             var newFlags = oldFlags
-            newFlags = if (
-                newFlags.hasBits(PermissionFlags.ROLE) || newFlags.hasBits(PermissionFlags.PREGRANT)
-            ) {
-                newFlags or PermissionFlags.RUNTIME_GRANTED
-            } else {
-                newFlags andInv PermissionFlags.RUNTIME_GRANTED
-            }
+            newFlags =
+                if (
+                    newFlags.hasBits(PermissionFlags.ROLE) ||
+                        newFlags.hasBits(PermissionFlags.PREGRANT)
+                ) {
+                    newFlags or PermissionFlags.RUNTIME_GRANTED
+                } else {
+                    newFlags andInv PermissionFlags.RUNTIME_GRANTED
+                }
             newFlags = newFlags andInv USER_SETTABLE_MASK
             if (newFlags.hasBits(PermissionFlags.LEGACY_GRANTED)) {
                 newFlags = newFlags or PermissionFlags.IMPLICIT
@@ -285,24 +297,32 @@
             if (!canAdoptPermissions(packageName, originalPackageName)) {
                 return@forEachIndexed
             }
-            newState.systemState.permissions.forEachIndexed permissions@ {
-                permissionIndex, permissionName, oldPermission ->
+            newState.systemState.permissions.forEachIndexed permissions@{
+                permissionIndex,
+                permissionName,
+                oldPermission ->
                 if (oldPermission.packageName != originalPackageName) {
                     return@permissions
                 }
                 @Suppress("DEPRECATION")
-                val newPermissionInfo = PermissionInfo().apply {
-                    name = oldPermission.permissionInfo.name
-                    this.packageName = packageName
-                    protectionLevel = oldPermission.permissionInfo.protectionLevel
-                }
+                val newPermissionInfo =
+                    PermissionInfo().apply {
+                        name = oldPermission.permissionInfo.name
+                        this.packageName = packageName
+                        protectionLevel = oldPermission.permissionInfo.protectionLevel
+                    }
                 // Different from the old implementation, which removes the GIDs upon permission
                 // adoption, but adds them back on the next boot, we now just consistently keep the
                 // GIDs.
-                val newPermission = oldPermission.copy(
-                    permissionInfo = newPermissionInfo, isReconciled = false, appId = 0
-                )
-                newState.mutateSystemState().mutatePermissions()
+                val newPermission =
+                    oldPermission.copy(
+                        permissionInfo = newPermissionInfo,
+                        isReconciled = false,
+                        appId = 0
+                    )
+                newState
+                    .mutateSystemState()
+                    .mutatePermissions()
                     .putAt(permissionIndex, newPermission)
                 changedPermissionNames += permissionName
             }
@@ -313,18 +333,20 @@
         packageName: String,
         originalPackageName: String
     ): Boolean {
-        val originalPackageState = newState.externalState.packageStates[originalPackageName]
-            ?: return false
+        val originalPackageState =
+            newState.externalState.packageStates[originalPackageName] ?: return false
         if (!originalPackageState.isSystem) {
             Slog.w(
-                LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" +
+                LOG_TAG,
+                "Unable to adopt permissions from $originalPackageName to $packageName:" +
                     " original package not in system partition"
             )
             return false
         }
         if (originalPackageState.androidPackage != null) {
             Slog.w(
-                LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" +
+                LOG_TAG,
+                "Unable to adopt permissions from $originalPackageName to $packageName:" +
                     " original package still exists"
             )
             return false
@@ -339,20 +361,25 @@
         val isInstantApp = packageState.userStates.allIndexed { _, _, it -> it.isInstantApp }
         if (isInstantApp) {
             Slog.w(
-                LOG_TAG, "Ignoring permission groups declared in package" +
+                LOG_TAG,
+                "Ignoring permission groups declared in package" +
                     " ${packageState.packageName}: instant apps cannot declare permission groups"
             )
             return
         }
         packageState.androidPackage!!.permissionGroups.forEachIndexed { _, parsedPermissionGroup ->
-            val newPermissionGroup = PackageInfoUtils.generatePermissionGroupInfo(
-                parsedPermissionGroup, PackageManager.GET_META_DATA.toLong()
-            )!!
+            val newPermissionGroup =
+                PackageInfoUtils.generatePermissionGroupInfo(
+                    parsedPermissionGroup,
+                    PackageManager.GET_META_DATA.toLong()
+                )!!
             // TODO: Clear permission state on group take-over?
             val permissionGroupName = newPermissionGroup.name
             val oldPermissionGroup = newState.systemState.permissionGroups[permissionGroupName]
-            if (oldPermissionGroup != null &&
-                newPermissionGroup.packageName != oldPermissionGroup.packageName) {
+            if (
+                oldPermissionGroup != null &&
+                    newPermissionGroup.packageName != oldPermissionGroup.packageName
+            ) {
                 val newPackageName = newPermissionGroup.packageName
                 val oldPackageName = oldPermissionGroup.packageName
                 // Different from the old implementation, which defines permission group on
@@ -361,7 +388,8 @@
                 // to permissions so that we no longer need to rely on the scan order.
                 if (!packageState.isSystem) {
                     Slog.w(
-                        LOG_TAG, "Ignoring permission group $permissionGroupName declared in" +
+                        LOG_TAG,
+                        "Ignoring permission group $permissionGroupName declared in" +
                             " package $newPackageName: already declared in another" +
                             " package $oldPackageName"
                     )
@@ -369,14 +397,16 @@
                 }
                 if (newState.externalState.packageStates[oldPackageName]?.isSystem == true) {
                     Slog.w(
-                        LOG_TAG, "Ignoring permission group $permissionGroupName declared in" +
+                        LOG_TAG,
+                        "Ignoring permission group $permissionGroupName declared in" +
                             " system package $newPackageName: already declared in another" +
                             " system package $oldPackageName"
                     )
                     return@forEachIndexed
                 }
                 Slog.w(
-                    LOG_TAG, "Overriding permission group $permissionGroupName with" +
+                    LOG_TAG,
+                    "Overriding permission group $permissionGroupName with" +
                         " new declaration in system package $newPackageName: originally" +
                         " declared in another package $oldPackageName"
                 )
@@ -393,20 +423,23 @@
         val androidPackage = packageState.androidPackage!!
         // This may not be the same package as the old permission because the old permission owner
         // can be different, hence using this somewhat strange name to prevent misuse.
-        val oldNewPackage = oldState.externalState.packageStates[packageState.packageName]
-            ?.androidPackage
-        val isPackageSigningChanged = oldNewPackage != null &&
-                androidPackage.signingDetails != oldNewPackage.signingDetails
+        val oldNewPackage =
+            oldState.externalState.packageStates[packageState.packageName]?.androidPackage
+        val isPackageSigningChanged =
+            oldNewPackage != null && androidPackage.signingDetails != oldNewPackage.signingDetails
         androidPackage.permissions.forEachIndexed { _, parsedPermission ->
-            val newPermissionInfo = PackageInfoUtils.generatePermissionInfo(
-                parsedPermission, PackageManager.GET_META_DATA.toLong()
-            )!!
+            val newPermissionInfo =
+                PackageInfoUtils.generatePermissionInfo(
+                    parsedPermission,
+                    PackageManager.GET_META_DATA.toLong()
+                )!!
             val permissionName = newPermissionInfo.name
-            val oldPermission = if (parsedPermission.isTree) {
-                newState.systemState.permissionTrees[permissionName]
-            } else {
-                newState.systemState.permissions[permissionName]
-            }
+            val oldPermission =
+                if (parsedPermission.isTree) {
+                    newState.systemState.permissionTrees[permissionName]
+                } else {
+                    newState.systemState.permissions[permissionName]
+                }
             // Different from the old implementation, which may add an (incomplete) signature
             // permission inside another package's permission tree, we now consistently ignore such
             // permissions.
@@ -414,128 +447,152 @@
             val newPackageName = newPermissionInfo.packageName
             if (permissionTree != null && newPackageName != permissionTree.packageName) {
                 Slog.w(
-                    LOG_TAG, "Ignoring permission $permissionName declared in package" +
+                    LOG_TAG,
+                    "Ignoring permission $permissionName declared in package" +
                         " $newPackageName: base permission tree ${permissionTree.name} is" +
                         " declared in another package ${permissionTree.packageName}"
                 )
                 return@forEachIndexed
             }
-            val newPermission = if (oldPermission != null &&
-                newPackageName != oldPermission.packageName) {
-                val oldPackageName = oldPermission.packageName
-                // Only allow system apps to redefine non-system permissions.
-                if (!packageState.isSystem) {
-                    Slog.w(
-                        LOG_TAG, "Ignoring permission $permissionName declared in package" +
-                            " $newPackageName: already declared in another package" +
-                            " $oldPackageName"
-                    )
-                    return@forEachIndexed
-                }
-                if (oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled) {
-                    // It's a config permission and has no owner, take ownership now.
-                    oldPermission.copy(
-                        permissionInfo = newPermissionInfo, isReconciled = true,
-                        type = Permission.TYPE_MANIFEST, appId = packageState.appId
-                    )
-                } else if (newState.externalState.packageStates[oldPackageName]?.isSystem != true) {
-                    Slog.w(
-                        LOG_TAG, "Overriding permission $permissionName with new declaration in" +
-                            " system package $newPackageName: originally declared in another" +
-                            " package $oldPackageName"
-                    )
-                    // Remove permission state on owner change.
-                    newState.externalState.userIds.forEachIndexed { _, userId ->
-                        newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ ->
-                            setPermissionFlags(appId, userId, permissionName, 0)
-                        }
-                    }
-                    // Different from the old implementation, which removes the GIDs upon permission
-                    // override, but adds them back on the next boot, we now just consistently keep
-                    // the GIDs.
-                    Permission(
-                        newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId,
-                        oldPermission.gids, oldPermission.areGidsPerUser
-                    )
-                } else {
-                    Slog.w(
-                        LOG_TAG, "Ignoring permission $permissionName declared in system package" +
-                            " $newPackageName: already declared in another system package" +
-                            " $oldPackageName"
-                    )
-                    return@forEachIndexed
-                }
-            } else {
-                if (oldPermission != null && oldPermission.isReconciled) {
-                    val isPermissionGroupChanged = newPermissionInfo.isRuntime &&
-                        newPermissionInfo.group != null &&
-                        newPermissionInfo.group != oldPermission.groupName
-                    val isPermissionProtectionChanged =
-                        oldPermission.type != Permission.TYPE_CONFIG && (
-                            (newPermissionInfo.isRuntime && !oldPermission.isRuntime) ||
-                                (newPermissionInfo.isInternal && !oldPermission.isInternal)
+            val newPermission =
+                if (oldPermission != null && newPackageName != oldPermission.packageName) {
+                    val oldPackageName = oldPermission.packageName
+                    // Only allow system apps to redefine non-system permissions.
+                    if (!packageState.isSystem) {
+                        Slog.w(
+                            LOG_TAG,
+                            "Ignoring permission $permissionName declared in package" +
+                                " $newPackageName: already declared in another package" +
+                                " $oldPackageName"
                         )
-                    if (isPermissionGroupChanged || isPermissionProtectionChanged) {
+                        return@forEachIndexed
+                    }
+                    if (
+                        oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled
+                    ) {
+                        // It's a config permission and has no owner, take ownership now.
+                        oldPermission.copy(
+                            permissionInfo = newPermissionInfo,
+                            isReconciled = true,
+                            type = Permission.TYPE_MANIFEST,
+                            appId = packageState.appId
+                        )
+                    } else if (
+                        newState.externalState.packageStates[oldPackageName]?.isSystem != true
+                    ) {
+                        Slog.w(
+                            LOG_TAG,
+                            "Overriding permission $permissionName with new declaration in" +
+                                " system package $newPackageName: originally declared in another" +
+                                " package $oldPackageName"
+                        )
+                        // Remove permission state on owner change.
                         newState.externalState.userIds.forEachIndexed { _, userId ->
                             newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ ->
-                                if (isPermissionGroupChanged) {
-                                    // We might auto-grant permissions if any permission of
-                                    // the group is already granted. Hence if the group of
-                                    // a granted permission changes we need to revoke it to
-                                    // avoid having permissions of the new group auto-granted.
-                                    Slog.w(
-                                        LOG_TAG, "Revoking runtime permission $permissionName for" +
-                                            " appId $appId and userId $userId as the permission" +
-                                            " group changed from ${oldPermission.groupName}" +
-                                            " to ${newPermissionInfo.group}"
-                                    )
-                                }
-                                if (isPermissionProtectionChanged) {
-                                    Slog.w(
-                                        LOG_TAG, "Revoking permission $permissionName for" +
-                                            " appId $appId and userId $userId as the permission" +
-                                            " protection changed."
-                                    )
-                                }
                                 setPermissionFlags(appId, userId, permissionName, 0)
                             }
                         }
+                        // Different from the old implementation, which removes the GIDs upon
+                        // permission
+                        // override, but adds them back on the next boot, we now just consistently
+                        // keep
+                        // the GIDs.
+                        Permission(
+                            newPermissionInfo,
+                            true,
+                            Permission.TYPE_MANIFEST,
+                            packageState.appId,
+                            oldPermission.gids,
+                            oldPermission.areGidsPerUser
+                        )
+                    } else {
+                        Slog.w(
+                            LOG_TAG,
+                            "Ignoring permission $permissionName declared in system package" +
+                                " $newPackageName: already declared in another system package" +
+                                " $oldPackageName"
+                        )
+                        return@forEachIndexed
+                    }
+                } else {
+                    if (oldPermission != null && oldPermission.isReconciled) {
+                        val isPermissionGroupChanged =
+                            newPermissionInfo.isRuntime &&
+                                newPermissionInfo.group != null &&
+                                newPermissionInfo.group != oldPermission.groupName
+                        val isPermissionProtectionChanged =
+                            oldPermission.type != Permission.TYPE_CONFIG &&
+                                ((newPermissionInfo.isRuntime && !oldPermission.isRuntime) ||
+                                    (newPermissionInfo.isInternal && !oldPermission.isInternal))
+                        if (isPermissionGroupChanged || isPermissionProtectionChanged) {
+                            newState.externalState.userIds.forEachIndexed { _, userId ->
+                                newState.externalState.appIdPackageNames.forEachIndexed {
+                                    _,
+                                    appId,
+                                    _ ->
+                                    if (isPermissionGroupChanged) {
+                                        // We might auto-grant permissions if any permission of
+                                        // the group is already granted. Hence if the group of
+                                        // a granted permission changes we need to revoke it to
+                                        // avoid having permissions of the new group auto-granted.
+                                        Slog.w(
+                                            LOG_TAG,
+                                            "Revoking runtime permission $permissionName for" +
+                                                " appId $appId and userId $userId as the permission" +
+                                                " group changed from ${oldPermission.groupName}" +
+                                                " to ${newPermissionInfo.group}"
+                                        )
+                                    }
+                                    if (isPermissionProtectionChanged) {
+                                        Slog.w(
+                                            LOG_TAG,
+                                            "Revoking permission $permissionName for" +
+                                                " appId $appId and userId $userId as the permission" +
+                                                " protection changed."
+                                        )
+                                    }
+                                    setPermissionFlags(appId, userId, permissionName, 0)
+                                }
+                            }
+                        }
+                    }
+
+                    // Different from the old implementation, which doesn't update the permission
+                    // definition upon app update, but does update it on the next boot, we now
+                    // consistently update the permission definition upon app update.
+                    @Suppress("IfThenToElvis")
+                    if (oldPermission != null) {
+                        oldPermission.copy(
+                            permissionInfo = newPermissionInfo,
+                            isReconciled = true,
+                            type = Permission.TYPE_MANIFEST,
+                            appId = packageState.appId
+                        )
+                    } else {
+                        Permission(
+                            newPermissionInfo,
+                            true,
+                            Permission.TYPE_MANIFEST,
+                            packageState.appId
+                        )
                     }
                 }
 
-                // Different from the old implementation, which doesn't update the permission
-                // definition upon app update, but does update it on the next boot, we now
-                // consistently update the permission definition upon app update.
-                @Suppress("IfThenToElvis")
-                if (oldPermission != null) {
-                    oldPermission.copy(
-                        permissionInfo = newPermissionInfo, isReconciled = true,
-                        type = Permission.TYPE_MANIFEST, appId = packageState.appId
-                    )
-                } else {
-                    Permission(
-                        newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId
-                    )
-                }
-            }
-
             if (parsedPermission.isTree) {
                 newState.mutateSystemState().mutatePermissionTrees()[permissionName] = newPermission
             } else {
                 newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission
-                val isPermissionChanged = oldPermission == null ||
-                    newPackageName != oldPermission.packageName ||
-                    newPermission.protectionLevel != oldPermission.protectionLevel || (
-                        oldPermission.isReconciled && (
-                            (newPermission.isSignature && isPackageSigningChanged) || (
-                                newPermission.isKnownSigner &&
-                                    newPermission.knownCerts != oldPermission.knownCerts
-                            ) || (
-                                newPermission.isRuntime && newPermission.groupName != null &&
-                                    newPermission.groupName != oldPermission.groupName
-                            )
-                        )
-                    )
+                val isPermissionChanged =
+                    oldPermission == null ||
+                        newPackageName != oldPermission.packageName ||
+                        newPermission.protectionLevel != oldPermission.protectionLevel ||
+                        (oldPermission.isReconciled &&
+                            ((newPermission.isSignature && isPackageSigningChanged) ||
+                                (newPermission.isKnownSigner &&
+                                    newPermission.knownCerts != oldPermission.knownCerts) ||
+                                (newPermission.isRuntime &&
+                                    newPermission.groupName != null &&
+                                    newPermission.groupName != oldPermission.groupName)))
                 if (isPermissionChanged) {
                     changedPermissionNames += permissionName
                 }
@@ -552,39 +609,47 @@
         if (packageState != null && androidPackage == null) {
             return
         }
-        val disabledSystemPackage = newState.externalState.disabledSystemPackageStates[packageName]
-            ?.androidPackage
+        val disabledSystemPackage =
+            newState.externalState.disabledSystemPackageStates[packageName]?.androidPackage
         // Unlike in the previous implementation, we now also retain permission trees defined by
         // disabled system packages for consistency with permissions.
         newState.systemState.permissionTrees.forEachReversedIndexed {
-            permissionTreeIndex, permissionTreeName, permissionTree ->
-            if (permissionTree.packageName == packageName && (
-                packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
-                    it.isTree && it.name == permissionTreeName
-                }
-            ) && (
-                disabledSystemPackage?.permissions?.anyIndexed { _, it ->
-                    it.isTree && it.name == permissionTreeName
-                } != true
-            )) {
+            permissionTreeIndex,
+            permissionTreeName,
+            permissionTree ->
+            if (
+                permissionTree.packageName == packageName &&
+                    (packageState == null ||
+                        androidPackage!!.permissions.noneIndexed { _, it ->
+                            it.isTree && it.name == permissionTreeName
+                        }) &&
+                    (disabledSystemPackage?.permissions?.anyIndexed { _, it ->
+                        it.isTree && it.name == permissionTreeName
+                    } != true)
+            ) {
                 newState.mutateSystemState().mutatePermissionTrees().removeAt(permissionTreeIndex)
             }
         }
 
         newState.systemState.permissions.forEachReversedIndexed {
-            permissionIndex, permissionName, permission ->
+            permissionIndex,
+            permissionName,
+            permission ->
             val updatedPermission = updatePermissionIfDynamic(permission)
-            newState.mutateSystemState().mutatePermissions()
+            newState
+                .mutateSystemState()
+                .mutatePermissions()
                 .putAt(permissionIndex, updatedPermission)
-            if (updatedPermission.packageName == packageName && (
-                packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
-                    !it.isTree && it.name == permissionName
-                }
-            ) && (
-                disabledSystemPackage?.permissions?.anyIndexed { _, it ->
-                    !it.isTree && it.name == permissionName
-                } != true
-            )) {
+            if (
+                updatedPermission.packageName == packageName &&
+                    (packageState == null ||
+                        androidPackage!!.permissions.noneIndexed { _, it ->
+                            !it.isTree && it.name == permissionName
+                        }) &&
+                    (disabledSystemPackage?.permissions?.anyIndexed { _, it ->
+                        !it.isTree && it.name == permissionName
+                    } != true)
+            ) {
                 // Different from the old implementation where we keep the permission state if the
                 // permission is declared by a disabled system package (ag/15189282), we now
                 // shouldn't be notified when the updated system package is removed but the disabled
@@ -608,9 +673,12 @@
         val permissionTree = findPermissionTree(permission.name) ?: return permission
         @Suppress("DEPRECATION")
         return permission.copy(
-            permissionInfo = PermissionInfo(permission.permissionInfo).apply {
-                packageName = permissionTree.packageName
-            }, appId = permissionTree.appId, isReconciled = true
+            permissionInfo =
+                PermissionInfo(permission.permissionInfo).apply {
+                    packageName = permissionTree.packageName
+                },
+            appId = permissionTree.appId,
+            isReconciled = true
         )
     }
 
@@ -636,8 +704,9 @@
     }
 
     private fun MutateStateScope.revokePermissionsOnPackageUpdate(appId: Int) {
-        val hasOldPackage = appId in oldState.externalState.appIdPackageNames &&
-            anyPackageInAppId(appId, oldState) { true }
+        val hasOldPackage =
+            appId in oldState.externalState.appIdPackageNames &&
+                anyPackageInAppId(appId, oldState) { true }
         if (!hasOldPackage) {
             // Don't revoke anything if this isn't a package update, i.e. if information about the
             // old package isn't available. Notably, this also means skipping packages changed via
@@ -650,46 +719,58 @@
         // app updated in an attempt to get unscoped storage. If so, revoke all storage permissions.
         val oldTargetSdkVersion =
             reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, oldState) {
-                targetSdkVersion, packageState ->
+                targetSdkVersion,
+                packageState ->
                 targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion)
             }
         val newTargetSdkVersion =
             reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, newState) {
-                targetSdkVersion, packageState ->
+                targetSdkVersion,
+                packageState ->
                 targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion)
             }
         @Suppress("ConvertTwoComparisonsToRangeCheck")
-        val isTargetSdkVersionDowngraded = oldTargetSdkVersion >= Build.VERSION_CODES.Q &&
-            newTargetSdkVersion < Build.VERSION_CODES.Q
+        val isTargetSdkVersionDowngraded =
+            oldTargetSdkVersion >= Build.VERSION_CODES.Q &&
+                newTargetSdkVersion < Build.VERSION_CODES.Q
         @Suppress("ConvertTwoComparisonsToRangeCheck")
-        val isTargetSdkVersionUpgraded = oldTargetSdkVersion < Build.VERSION_CODES.Q &&
-            newTargetSdkVersion >= Build.VERSION_CODES.Q
-        val oldIsRequestLegacyExternalStorage = anyPackageInAppId(appId, oldState) {
-            it.androidPackage!!.isRequestLegacyExternalStorage
-        }
-        val newIsRequestLegacyExternalStorage = anyPackageInAppId(appId, newState) {
-            it.androidPackage!!.isRequestLegacyExternalStorage
-        }
-        val isNewlyRequestingLegacyExternalStorage = !isTargetSdkVersionUpgraded &&
-            !oldIsRequestLegacyExternalStorage && newIsRequestLegacyExternalStorage
-        val shouldRevokeStorageAndMediaPermissions = isNewlyRequestingLegacyExternalStorage ||
-            isTargetSdkVersionDowngraded
+        val isTargetSdkVersionUpgraded =
+            oldTargetSdkVersion < Build.VERSION_CODES.Q &&
+                newTargetSdkVersion >= Build.VERSION_CODES.Q
+        val oldIsRequestLegacyExternalStorage =
+            anyPackageInAppId(appId, oldState) {
+                it.androidPackage!!.isRequestLegacyExternalStorage
+            }
+        val newIsRequestLegacyExternalStorage =
+            anyPackageInAppId(appId, newState) {
+                it.androidPackage!!.isRequestLegacyExternalStorage
+            }
+        val isNewlyRequestingLegacyExternalStorage =
+            !isTargetSdkVersionUpgraded &&
+                !oldIsRequestLegacyExternalStorage &&
+                newIsRequestLegacyExternalStorage
+        val shouldRevokeStorageAndMediaPermissions =
+            isNewlyRequestingLegacyExternalStorage || isTargetSdkVersionDowngraded
         if (shouldRevokeStorageAndMediaPermissions) {
             newState.userStates.forEachIndexed { _, userId, userState ->
                 userState.appIdPermissionFlags[appId]?.forEachReversedIndexed {
-                    _, permissionName, oldFlags ->
+                    _,
+                    permissionName,
+                    oldFlags ->
                     // Do not revoke the permission during an upgrade if it's POLICY_FIXED or
                     // SYSTEM_FIXED. Otherwise the user cannot grant back the permission.
-                    if (permissionName in STORAGE_AND_MEDIA_PERMISSIONS &&
-                        oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED) &&
-                        !oldFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK)) {
+                    if (
+                        permissionName in STORAGE_AND_MEDIA_PERMISSIONS &&
+                            oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED) &&
+                            !oldFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK)
+                    ) {
                         Slog.v(
-                            LOG_TAG, "Revoking storage permission: $permissionName for appId: " +
+                            LOG_TAG,
+                            "Revoking storage permission: $permissionName for appId: " +
                                 " $appId and user: $userId"
                         )
-                        val newFlags = oldFlags andInv (
-                            PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK
-                        )
+                        val newFlags =
+                            oldFlags andInv (PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK)
                         setPermissionFlags(appId, userId, permissionName, newFlags)
                     }
                 }
@@ -704,9 +785,10 @@
         val externalState = newState.externalState
         externalState.userIds.forEachIndexed { _, userId ->
             externalState.appIdPackageNames.forEachIndexed { _, appId, _ ->
-                val isPermissionRequested = anyPackageInAppId(appId) {
-                    permissionName in it.androidPackage!!.requestedPermissions
-                }
+                val isPermissionRequested =
+                    anyPackageInAppId(appId) {
+                        permissionName in it.androidPackage!!.requestedPermissions
+                    }
                 if (isPermissionRequested) {
                     evaluatePermissionState(appId, userId, permissionName, installedPackageState)
                 }
@@ -720,7 +802,9 @@
     ) {
         newState.externalState.userIds.forEachIndexed { _, userId ->
             evaluateAllPermissionStatesForPackageAndUser(
-                packageState, userId, installedPackageState
+                packageState,
+                userId,
+                installedPackageState
             )
         }
     }
@@ -732,7 +816,10 @@
     ) {
         packageState.androidPackage?.requestedPermissions?.forEach { permissionName ->
             evaluatePermissionState(
-                packageState.appId, userId, permissionName, installedPackageState
+                packageState.appId,
+                userId,
+                permissionName,
+                installedPackageState
             )
         }
     }
@@ -779,57 +866,71 @@
             val wasGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED)
             if (!wasGranted) {
                 val wasRevoked = oldFlags.hasBits(PermissionFlags.INSTALL_REVOKED)
-                val isRequestedByInstalledPackage = installedPackageState != null &&
-                    permissionName in installedPackageState.androidPackage!!.requestedPermissions
+                val isRequestedByInstalledPackage =
+                    installedPackageState != null &&
+                        permissionName in
+                            installedPackageState.androidPackage!!.requestedPermissions
                 val isRequestedBySystemPackage =
                     requestingPackageStates.anyIndexed { _, it -> it.isSystem }
-                val isCompatibilityPermission = requestingPackageStates.anyIndexed { _, it ->
-                    isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName)
-                }
+                val isCompatibilityPermission =
+                    requestingPackageStates.anyIndexed { _, it ->
+                        isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName)
+                    }
                 // If this is an existing, non-system package,
                 // then we can't add any new permissions to it.
                 // Except if this is a permission that was added to the platform
-                var newFlags = if (!wasRevoked || isRequestedByInstalledPackage ||
-                    isRequestedBySystemPackage || isCompatibilityPermission) {
-                    PermissionFlags.INSTALL_GRANTED
-                } else {
-                    PermissionFlags.INSTALL_REVOKED
-                }
+                var newFlags =
+                    if (
+                        !wasRevoked ||
+                            isRequestedByInstalledPackage ||
+                            isRequestedBySystemPackage ||
+                            isCompatibilityPermission
+                    ) {
+                        PermissionFlags.INSTALL_GRANTED
+                    } else {
+                        PermissionFlags.INSTALL_REVOKED
+                    }
                 if (permission.isAppOp) {
-                    newFlags = newFlags or (
-                        oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)
-                    )
+                    newFlags =
+                        newFlags or
+                            (oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET))
                 }
                 setPermissionFlags(appId, userId, permissionName, newFlags)
             }
         } else if (permission.isSignature || permission.isInternal) {
             val wasProtectionGranted = oldFlags.hasBits(PermissionFlags.PROTECTION_GRANTED)
-            var newFlags = if (hasMissingPackage && wasProtectionGranted) {
-                // Keep the non-runtime permission grants for shared UID with missing androidPackage
-                PermissionFlags.PROTECTION_GRANTED
-            } else {
-                val mayGrantByPrivileged = !permission.isPrivileged ||
-                    requestingPackageStates.anyIndexed { _, it ->
-                        checkPrivilegedPermissionAllowlist(it, permission)
-                    }
-                val shouldGrantBySignature = permission.isSignature &&
-                    requestingPackageStates.anyIndexed { _, it ->
-                        shouldGrantPermissionBySignature(it, permission)
-                    }
-                val shouldGrantByProtectionFlags = requestingPackageStates.anyIndexed { _, it ->
-                    shouldGrantPermissionByProtectionFlags(it, permission)
-                }
-                if (mayGrantByPrivileged &&
-                    (shouldGrantBySignature || shouldGrantByProtectionFlags)) {
+            var newFlags =
+                if (hasMissingPackage && wasProtectionGranted) {
+                    // Keep the non-runtime permission grants for shared UID with missing
+                    // androidPackage
                     PermissionFlags.PROTECTION_GRANTED
                 } else {
-                    0
+                    val mayGrantByPrivileged =
+                        !permission.isPrivileged ||
+                            requestingPackageStates.anyIndexed { _, it ->
+                                checkPrivilegedPermissionAllowlist(it, permission)
+                            }
+                    val shouldGrantBySignature =
+                        permission.isSignature &&
+                            requestingPackageStates.anyIndexed { _, it ->
+                                shouldGrantPermissionBySignature(it, permission)
+                            }
+                    val shouldGrantByProtectionFlags =
+                        requestingPackageStates.anyIndexed { _, it ->
+                            shouldGrantPermissionByProtectionFlags(it, permission)
+                        }
+                    if (
+                        mayGrantByPrivileged &&
+                            (shouldGrantBySignature || shouldGrantByProtectionFlags)
+                    ) {
+                        PermissionFlags.PROTECTION_GRANTED
+                    } else {
+                        0
+                    }
                 }
-            }
             if (permission.isAppOp) {
-                newFlags = newFlags or (
-                    oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)
-                )
+                newFlags =
+                    newFlags or (oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET))
             }
             // Different from the old implementation, which seemingly allows granting an
             // unallowlisted privileged permission via development or role but revokes it upon next
@@ -840,9 +941,9 @@
                 newFlags = newFlags or (oldFlags and PermissionFlags.RUNTIME_GRANTED)
             }
             if (permission.isRole) {
-                newFlags = newFlags or (
-                    oldFlags and (PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED)
-                )
+                newFlags =
+                    newFlags or
+                        (oldFlags and (PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED))
             }
             setPermissionFlags(appId, userId, permissionName, newFlags)
         } else if (permission.isRuntime) {
@@ -850,7 +951,9 @@
             val wasRevoked = newFlags != 0 && !PermissionFlags.isPermissionGranted(newFlags)
             val targetSdkVersion =
                 requestingPackageStates.reduceIndexed(Build.VERSION_CODES.CUR_DEVELOPMENT) {
-                    targetSdkVersion, _, packageState ->
+                    targetSdkVersion,
+                    _,
+                    packageState ->
                     targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion)
                 }
             if (targetSdkVersion < Build.VERSION_CODES.M) {
@@ -883,23 +986,27 @@
                     }
                 }
                 val wasGrantedByImplicit = newFlags.hasBits(PermissionFlags.IMPLICIT_GRANTED)
-                val isLeanbackNotificationsPermission = newState.externalState.isLeanback &&
-                    permissionName in NOTIFICATIONS_PERMISSIONS
-                val isImplicitPermission = requestingPackageStates.anyIndexed { _, it ->
-                    permissionName in it.androidPackage!!.implicitPermissions
-                }
-                val sourcePermissions = newState.externalState
-                    .implicitToSourcePermissions[permissionName]
-                val isAnySourcePermissionNonRuntime = sourcePermissions?.anyIndexed {
-                    _, sourcePermissionName ->
-                    val sourcePermission = newState.systemState.permissions[sourcePermissionName]
-                    checkNotNull(sourcePermission) {
-                        "Unknown source permission $sourcePermissionName in split permissions"
+                val isLeanbackNotificationsPermission =
+                    newState.externalState.isLeanback && permissionName in NOTIFICATIONS_PERMISSIONS
+                val isImplicitPermission =
+                    requestingPackageStates.anyIndexed { _, it ->
+                        permissionName in it.androidPackage!!.implicitPermissions
                     }
-                    !sourcePermission.isRuntime
-                } ?: false
-                val shouldGrantByImplicit = isLeanbackNotificationsPermission ||
-                    (isImplicitPermission && isAnySourcePermissionNonRuntime)
+                val sourcePermissions =
+                    newState.externalState.implicitToSourcePermissions[permissionName]
+                val isAnySourcePermissionNonRuntime =
+                    sourcePermissions?.anyIndexed { _, sourcePermissionName ->
+                        val sourcePermission =
+                            newState.systemState.permissions[sourcePermissionName]
+                        checkNotNull(sourcePermission) {
+                            "Unknown source permission $sourcePermissionName in split permissions"
+                        }
+                        !sourcePermission.isRuntime
+                    }
+                        ?: false
+                val shouldGrantByImplicit =
+                    isLeanbackNotificationsPermission ||
+                        (isImplicitPermission && isAnySourcePermissionNonRuntime)
                 if (shouldGrantByImplicit) {
                     newFlags = newFlags or PermissionFlags.IMPLICIT_GRANTED
                     if (wasRevoked) {
@@ -907,26 +1014,31 @@
                     }
                 } else {
                     newFlags = newFlags andInv PermissionFlags.IMPLICIT_GRANTED
-                    if ((wasGrantedByLegacy || wasGrantedByImplicit) &&
-                        newFlags.hasBits(PermissionFlags.APP_OP_REVOKED)) {
+                    if (
+                        (wasGrantedByLegacy || wasGrantedByImplicit) &&
+                            newFlags.hasBits(PermissionFlags.APP_OP_REVOKED)
+                    ) {
                         // The permission was granted from a compatibility grant or an implicit
                         // grant, however this flag might still be set if the user denied this
                         // permission in the settings. Hence upon app upgrade and when this
                         // permission is no longer LEGACY_GRANTED or IMPLICIT_GRANTED and we revoke
                         // the permission, we want to remove this flag so that the app can request
                         // the permission again.
-                        newFlags = newFlags andInv (
-                            PermissionFlags.RUNTIME_GRANTED or PermissionFlags.APP_OP_REVOKED
-                        )
+                        newFlags =
+                            newFlags andInv
+                                (PermissionFlags.RUNTIME_GRANTED or PermissionFlags.APP_OP_REVOKED)
                     }
                 }
                 if (!isImplicitPermission && hasImplicitFlag) {
                     newFlags = newFlags andInv PermissionFlags.IMPLICIT
                     var shouldRetainAsNearbyDevices = false
                     if (permissionName in NEARBY_DEVICES_PERMISSIONS) {
-                        val accessBackgroundLocationFlags = getPermissionFlags(
-                            appId, userId, Manifest.permission.ACCESS_BACKGROUND_LOCATION
-                        )
+                        val accessBackgroundLocationFlags =
+                            getPermissionFlags(
+                                appId,
+                                userId,
+                                Manifest.permission.ACCESS_BACKGROUND_LOCATION
+                            )
                         shouldRetainAsNearbyDevices =
                             PermissionFlags.isAppOpGranted(accessBackgroundLocationFlags) &&
                                 !accessBackgroundLocationFlags.hasBits(PermissionFlags.IMPLICIT)
@@ -937,46 +1049,57 @@
                             newFlags = newFlags or PermissionFlags.RUNTIME_GRANTED
                         }
                     } else {
-                        newFlags = newFlags andInv (
-                            PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET or
-                                PermissionFlags.USER_FIXED
-                        )
+                        newFlags =
+                            newFlags andInv
+                                (PermissionFlags.RUNTIME_GRANTED or
+                                    PermissionFlags.USER_SET or
+                                    PermissionFlags.USER_FIXED)
                     }
                 }
             }
 
             val wasExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
             val wasRestricted = newFlags.hasAnyBit(PermissionFlags.MASK_RESTRICTED)
-            val isExempt = if (permission.isHardOrSoftRestricted && !wasExempt && !wasRestricted) {
-                // All restricted permissions start as exempt. If there's an installer for the
-                // package, we will drop this UPGRADE_EXEMPT flag when we receive the
-                // onPackageInstalled() callback and set up the INSTALLER_EXEMPT flags.
-                // UPGRADE_EXEMPT is chosen instead of other flags because it is the same flag that
-                // was assigned to pre-installed apps in RuntimePermissionsUpgradeController, and to
-                // apps with missing permission state.
-                // This way we make sure both pre-installed apps, and apps updated/installed after
-                // a rollback snapshot is taken, can get the allowlist for permissions that won't be
-                // allowlisted otherwise.
-                newFlags = newFlags or PermissionFlags.UPGRADE_EXEMPT
-                true
-            } else {
-                wasExempt
-            }
-            newFlags = if (permission.isHardRestricted && !isExempt) {
-                newFlags or PermissionFlags.RESTRICTION_REVOKED
-            } else {
-                newFlags andInv PermissionFlags.RESTRICTION_REVOKED
-            }
-            newFlags = if (permission.isSoftRestricted && !isExempt) {
-                newFlags or PermissionFlags.SOFT_RESTRICTED
-            } else {
-                newFlags andInv PermissionFlags.SOFT_RESTRICTED
-            }
+            val isExempt =
+                if (permission.isHardOrSoftRestricted && !wasExempt && !wasRestricted) {
+                    // All restricted permissions start as exempt. If there's an installer for the
+                    // package, we will drop this UPGRADE_EXEMPT flag when we receive the
+                    // onPackageInstalled() callback and set up the INSTALLER_EXEMPT flags.
+                    // UPGRADE_EXEMPT is chosen instead of other flags because it is the same flag
+                    // that
+                    // was assigned to pre-installed apps in RuntimePermissionsUpgradeController,
+                    // and to
+                    // apps with missing permission state.
+                    // This way we make sure both pre-installed apps, and apps updated/installed
+                    // after
+                    // a rollback snapshot is taken, can get the allowlist for permissions that
+                    // won't be
+                    // allowlisted otherwise.
+                    newFlags = newFlags or PermissionFlags.UPGRADE_EXEMPT
+                    true
+                } else {
+                    wasExempt
+                }
+            newFlags =
+                if (permission.isHardRestricted && !isExempt) {
+                    newFlags or PermissionFlags.RESTRICTION_REVOKED
+                } else {
+                    newFlags andInv PermissionFlags.RESTRICTION_REVOKED
+                }
+            newFlags =
+                if (permission.isSoftRestricted && !isExempt) {
+                    newFlags or PermissionFlags.SOFT_RESTRICTED
+                } else {
+                    newFlags andInv PermissionFlags.SOFT_RESTRICTED
+                }
             setPermissionFlags(appId, userId, permissionName, newFlags)
         } else {
-            Slog.e(LOG_TAG, "Unknown protection level ${permission.protectionLevel}" +
-                "for permission ${permission.name} while evaluating permission state" +
-                "for appId $appId and userId $userId")
+            Slog.e(
+                LOG_TAG,
+                "Unknown protection level ${permission.protectionLevel}" +
+                    "for permission ${permission.name} while evaluating permission state" +
+                    "for appId $appId and userId $userId"
+            )
         }
     }
 
@@ -985,7 +1108,7 @@
         forEachPackageInAppId(appId) {
             implicitPermissions += it.androidPackage!!.implicitPermissions
         }
-        implicitPermissions.forEachIndexed implicitPermissions@ { _, implicitPermissionName ->
+        implicitPermissions.forEachIndexed implicitPermissions@{ _, implicitPermissionName ->
             val implicitPermission = newState.systemState.permissions[implicitPermissionName]
             checkNotNull(implicitPermission) {
                 "Unknown implicit permission $implicitPermissionName in split permissions"
@@ -999,10 +1122,11 @@
             if (!isNewPermission) {
                 return@implicitPermissions
             }
-            val sourcePermissions = newState.externalState
-                .implicitToSourcePermissions[implicitPermissionName] ?: return@implicitPermissions
+            val sourcePermissions =
+                newState.externalState.implicitToSourcePermissions[implicitPermissionName]
+                    ?: return@implicitPermissions
             var newFlags = getPermissionFlags(appId, userId, implicitPermissionName)
-            sourcePermissions.forEachIndexed sourcePermissions@ { _, sourcePermissionName ->
+            sourcePermissions.forEachIndexed sourcePermissions@{ _, sourcePermissionName ->
                 val sourcePermission = newState.systemState.permissions[sourcePermissionName]
                 checkNotNull(sourcePermission) {
                     "Unknown source permission $sourcePermissionName in split permissions"
@@ -1032,11 +1156,14 @@
         permissionName: String
     ): Boolean {
         for (compatibilityPermission in CompatibilityPermissionInfo.COMPAT_PERMS) {
-            if (compatibilityPermission.name == permissionName &&
-                androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion) {
+            if (
+                compatibilityPermission.name == permissionName &&
+                    androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion
+            ) {
                 Slog.i(
-                    LOG_TAG, "Auto-granting $permissionName to old package" +
-                    " ${androidPackage.packageName}"
+                    LOG_TAG,
+                    "Auto-granting $permissionName to old package" +
+                        " ${androidPackage.packageName}"
                 )
                 return true
             }
@@ -1058,15 +1185,23 @@
         //     and the defining package still trusts the old certificate for permissions
         // - or it shares the above relationships with the system package
         val packageSigningDetails = packageState.androidPackage!!.signingDetails
-        val sourceSigningDetails = newState.externalState
-            .packageStates[permission.packageName]?.androidPackage?.signingDetails
-        val platformSigningDetails = newState.externalState
-            .packageStates[PLATFORM_PACKAGE_NAME]!!.androidPackage!!.signingDetails
-        return sourceSigningDetails?.hasCommonSignerWithCapability(packageSigningDetails,
-            SigningDetails.CertCapabilities.PERMISSION) == true ||
+        val sourceSigningDetails =
+            newState.externalState.packageStates[permission.packageName]
+                ?.androidPackage
+                ?.signingDetails
+        val platformSigningDetails =
+            newState.externalState.packageStates[PLATFORM_PACKAGE_NAME]!!
+                .androidPackage!!
+                .signingDetails
+        return sourceSigningDetails?.hasCommonSignerWithCapability(
+            packageSigningDetails,
+            SigningDetails.CertCapabilities.PERMISSION
+        ) == true ||
             packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) ||
-            platformSigningDetails.checkCapability(packageSigningDetails,
-                    SigningDetails.CertCapabilities.PERMISSION)
+            platformSigningDetails.checkCapability(
+                packageSigningDetails,
+                SigningDetails.CertCapabilities.PERMISSION
+            )
     }
 
     private fun MutateStateScope.checkPrivilegedPermissionAllowlist(
@@ -1082,8 +1217,9 @@
         if (!(packageState.isSystem && packageState.isPrivileged)) {
             return true
         }
-        if (permission.packageName !in
-            newState.externalState.privilegedPermissionAllowlistPackages) {
+        if (
+            permission.packageName !in newState.externalState.privilegedPermissionAllowlistPackages
+        ) {
             return true
         }
         val allowlistState = getPrivilegedPermissionAllowlistState(packageState, permission.name)
@@ -1099,13 +1235,15 @@
             // Apps that are in updated apex's do not need to be allowlisted
             if (!packageState.isApkInUpdatedApex) {
                 Slog.w(
-                    LOG_TAG, "Privileged permission ${permission.name} for package" +
-                    " ${packageState.packageName} (${packageState.path}) not in" +
-                    " privileged permission allowlist"
+                    LOG_TAG,
+                    "Privileged permission ${permission.name} for package" +
+                        " ${packageState.packageName} (${packageState.path}) not in" +
+                        " privileged permission allowlist"
                 )
                 if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
-                    privilegedPermissionAllowlistViolations += "${packageState.packageName}" +
-                        " (${packageState.path}): ${permission.name}"
+                    privilegedPermissionAllowlistViolations +=
+                        "${packageState.packageName}" +
+                            " (${packageState.path}): ${permission.name}"
                 }
             }
         }
@@ -1124,32 +1262,40 @@
         val apexModuleName = packageState.apexModuleName
         val packageName = packageState.packageName
         return when {
-            packageState.isVendor -> permissionAllowlist.getVendorPrivilegedAppAllowlistState(
-                packageName, permissionName
-            )
-            packageState.isProduct -> permissionAllowlist.getProductPrivilegedAppAllowlistState(
-                packageName, permissionName
-            )
+            packageState.isVendor ->
+                permissionAllowlist.getVendorPrivilegedAppAllowlistState(
+                    packageName,
+                    permissionName
+                )
+            packageState.isProduct ->
+                permissionAllowlist.getProductPrivilegedAppAllowlistState(
+                    packageName,
+                    permissionName
+                )
             packageState.isSystemExt ->
                 permissionAllowlist.getSystemExtPrivilegedAppAllowlistState(
-                    packageName, permissionName
+                    packageName,
+                    permissionName
                 )
             apexModuleName != null -> {
-                val nonApexAllowlistState = permissionAllowlist.getPrivilegedAppAllowlistState(
-                    packageName, permissionName
-                )
+                val nonApexAllowlistState =
+                    permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName)
                 if (nonApexAllowlistState != null) {
                     // TODO(andreionea): Remove check as soon as all apk-in-apex
                     // permission allowlists are migrated.
                     Slog.w(
-                        LOG_TAG, "Package $packageName is an APK in APEX but has permission" +
+                        LOG_TAG,
+                        "Package $packageName is an APK in APEX but has permission" +
                             " allowlist on the system image, please bundle the allowlist in the" +
                             " $apexModuleName APEX instead"
                     )
                 }
-                val apexAllowlistState = permissionAllowlist.getApexPrivilegedAppAllowlistState(
-                    apexModuleName, packageName, permissionName
-                )
+                val apexAllowlistState =
+                    permissionAllowlist.getApexPrivilegedAppAllowlistState(
+                        apexModuleName,
+                        packageName,
+                        permissionName
+                    )
                 apexAllowlistState ?: nonApexAllowlistState
             }
             else -> permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName)
@@ -1208,18 +1354,19 @@
         val knownPackages = newState.externalState.knownPackages
         val packageName = packageState.packageName
         if ((permission.isPrivileged || permission.isOem) && packageState.isSystem) {
-            val shouldGrant = if (packageState.isUpdatedSystemApp) {
-                // For updated system applications, a privileged/oem permission
-                // is granted only if it had been defined by the original application.
-                val disabledSystemPackageState = newState.externalState
-                    .disabledSystemPackageStates[packageState.packageName]
-                val disabledSystemPackage = disabledSystemPackageState?.androidPackage
-                disabledSystemPackage != null &&
-                    permission.name in disabledSystemPackage.requestedPermissions &&
-                    shouldGrantPrivilegedOrOemPermission(disabledSystemPackageState, permission)
-            } else {
-                shouldGrantPrivilegedOrOemPermission(packageState, permission)
-            }
+            val shouldGrant =
+                if (packageState.isUpdatedSystemApp) {
+                    // For updated system applications, a privileged/oem permission
+                    // is granted only if it had been defined by the original application.
+                    val disabledSystemPackageState =
+                        newState.externalState.disabledSystemPackageStates[packageState.packageName]
+                    val disabledSystemPackage = disabledSystemPackageState?.androidPackage
+                    disabledSystemPackage != null &&
+                        permission.name in disabledSystemPackage.requestedPermissions &&
+                        shouldGrantPrivilegedOrOemPermission(disabledSystemPackageState, permission)
+                } else {
+                    shouldGrantPrivilegedOrOemPermission(packageState, permission)
+                }
             if (shouldGrant) {
                 return true
             }
@@ -1230,16 +1377,18 @@
             // we still want to blindly grant it to old apps.
             return true
         }
-        if (permission.isInstaller && (
-            packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER]!! ||
-                packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER]!!
-        )) {
+        if (
+            permission.isInstaller &&
+                (packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER]!! ||
+                    packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER]!!)
+        ) {
             // If this permission is to be granted to the system installer and
             // this app is an installer or permission controller, then it gets the permission.
             return true
         }
-        if (permission.isVerifier &&
-            packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]!!) {
+        if (
+            permission.isVerifier && packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]!!
+        ) {
             // If this permission is to be granted to the system verifier and
             // this app is a verifier, then it gets the permission.
             return true
@@ -1248,53 +1397,67 @@
             // Any pre-installed system app is allowed to get this permission.
             return true
         }
-        if (permission.isKnownSigner &&
-            androidPackage.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts)) {
+        if (
+            permission.isKnownSigner &&
+                androidPackage.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts)
+        ) {
             // If the permission is to be granted to a known signer then check if any of this
             // app's signing certificates are in the trusted certificate digest Set.
             return true
         }
-        if (permission.isSetup &&
-            packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]!!) {
+        if (
+            permission.isSetup && packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]!!
+        ) {
             // If this permission is to be granted to the system setup wizard and
             // this app is a setup wizard, then it gets the permission.
             return true
         }
-        if (permission.isSystemTextClassifier &&
-            packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]!!) {
+        if (
+            permission.isSystemTextClassifier &&
+                packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]!!
+        ) {
             // Special permissions for the system default text classifier.
             return true
         }
-        if (permission.isConfigurator &&
-            packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]!!) {
+        if (
+            permission.isConfigurator &&
+                packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]!!
+        ) {
             // Special permissions for the device configurator.
             return true
         }
-        if (permission.isIncidentReportApprover &&
-            packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]!!) {
+        if (
+            permission.isIncidentReportApprover &&
+                packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]!!
+        ) {
             // If this permission is to be granted to the incident report approver and
             // this app is the incident report approver, then it gets the permission.
             return true
         }
-        if (permission.isAppPredictor &&
-            packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]!!) {
+        if (
+            permission.isAppPredictor &&
+                packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]!!
+        ) {
             // Special permissions for the system app predictor.
             return true
         }
-        if (permission.isCompanion &&
-            packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]!!) {
+        if (
+            permission.isCompanion &&
+                packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]!!
+        ) {
             // Special permissions for the system companion device manager.
             return true
         }
-        if (permission.isRetailDemo &&
-            packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO]!!) {
+        if (
+            permission.isRetailDemo &&
+                packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO]!!
+        ) {
             // Special permission granted only to the OEM specified retail demo app.
             // Note that the original code was passing app ID as UID, so this behavior is kept
             // unchanged.
             return true
         }
-        if (permission.isRecents &&
-            packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]!!) {
+        if (permission.isRecents && packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]!!) {
             // Special permission for the recents app.
             return true
         }
@@ -1319,9 +1482,10 @@
                     // flag.
                     if (packageState.isVendor && !permission.isVendorPrivileged) {
                         Slog.w(
-                            LOG_TAG, "Permission $permissionName cannot be granted to privileged" +
-                            " vendor app $packageName because it isn't a vendorPrivileged" +
-                            " permission"
+                            LOG_TAG,
+                            "Permission $permissionName cannot be granted to privileged" +
+                                " vendor app $packageName because it isn't a vendorPrivileged" +
+                                " permission"
                         )
                         return false
                     }
@@ -1330,8 +1494,11 @@
             }
             permission.isOem -> {
                 if (packageState.isOem) {
-                    val allowlistState = newState.externalState.permissionAllowlist
-                        .getOemAppAllowlistState(packageName, permissionName)
+                    val allowlistState =
+                        newState.externalState.permissionAllowlist.getOemAppAllowlistState(
+                            packageName,
+                            permissionName
+                        )
                     checkNotNull(allowlistState) {
                         "OEM permission $permissionName requested by package" +
                             " $packageName must be explicitly declared granted or not"
@@ -1358,13 +1525,18 @@
             val appId = externalState.packageStates[packageName]?.appId ?: continue
             newState.userStates.forEachIndexed { _, userId, _ ->
                 evaluatePermissionState(
-                    appId, userId, Manifest.permission.PACKAGE_USAGE_STATS, null
+                    appId,
+                    userId,
+                    Manifest.permission.PACKAGE_USAGE_STATS,
+                    null
                 )
             }
         }
         if (!privilegedPermissionAllowlistViolations.isEmpty()) {
-            throw IllegalStateException("Signature|privileged permissions not in privileged" +
-                " permission allowlist: $privilegedPermissionAllowlistViolations")
+            throw IllegalStateException(
+                "Signature|privileged permissions not in privileged" +
+                    " permission allowlist: $privilegedPermissionAllowlistViolations"
+            )
         }
     }
 
@@ -1389,10 +1561,14 @@
 
     fun GetStateScope.findPermissionTree(permissionName: String): Permission? =
         state.systemState.permissionTrees.firstNotNullOfOrNullIndexed {
-                _, permissionTreeName, permissionTree ->
-            if (permissionName.startsWith(permissionTreeName) &&
-                permissionName.length > permissionTreeName.length &&
-                permissionName[permissionTreeName.length] == '.') {
+            _,
+            permissionTreeName,
+            permissionTree ->
+            if (
+                permissionName.startsWith(permissionTreeName) &&
+                    permissionName.length > permissionTreeName.length &&
+                    permissionName[permissionTreeName.length] == '.'
+            ) {
                 permissionTree
             } else {
                 null
@@ -1403,15 +1579,11 @@
         newState.mutateSystemState().mutatePermissionTrees()[permission.name] = permission
     }
 
-    /**
-     * returns all permission group definitions available in the system
-     */
+    /** returns all permission group definitions available in the system */
     fun GetStateScope.getPermissionGroups(): IndexedMap<String, PermissionGroupInfo> =
         state.systemState.permissionGroups
 
-    /**
-     * returns all permission definitions available in the system
-     */
+    /** returns all permission definitions available in the system */
     fun GetStateScope.getPermissions(): IndexedMap<String, Permission> =
         state.systemState.permissions
 
@@ -1430,11 +1602,8 @@
     fun GetStateScope.getUidPermissionFlags(appId: Int, userId: Int): IndexedMap<String, Int>? =
         state.userStates[userId]?.appIdPermissionFlags?.get(appId)
 
-    fun GetStateScope.getPermissionFlags(
-        appId: Int,
-        userId: Int,
-        permissionName: String
-    ): Int = getPermissionFlags(state, appId, userId, permissionName)
+    fun GetStateScope.getPermissionFlags(appId: Int, userId: Int, permissionName: String): Int =
+        getPermissionFlags(state, appId, userId, permissionName)
 
     private fun MutateStateScope.getOldStatePermissionFlags(
         appId: Int,
@@ -1465,8 +1634,10 @@
         flagMask: Int,
         flagValues: Int
     ): Boolean {
-        val oldFlags = newState.userStates[userId]!!.appIdPermissionFlags[appId]
-            .getWithDefault(permissionName, 0)
+        val oldFlags =
+            newState.userStates[userId]!!
+                .appIdPermissionFlags[appId]
+                .getWithDefault(permissionName, 0)
         val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask)
         if (oldFlags == newFlags) {
             return false
@@ -1517,38 +1688,37 @@
         private const val PLATFORM_PACKAGE_NAME = "android"
 
         // A set of permissions that we don't want to revoke when they are no longer implicit.
-        private val RETAIN_IMPLICIT_FLAGS_PERMISSIONS = indexedSetOf(
-            Manifest.permission.ACCESS_MEDIA_LOCATION,
-            Manifest.permission.ACTIVITY_RECOGNITION,
-            Manifest.permission.READ_MEDIA_AUDIO,
-            Manifest.permission.READ_MEDIA_IMAGES,
-            Manifest.permission.READ_MEDIA_VIDEO,
-        )
+        private val RETAIN_IMPLICIT_FLAGS_PERMISSIONS =
+            indexedSetOf(
+                Manifest.permission.ACCESS_MEDIA_LOCATION,
+                Manifest.permission.ACTIVITY_RECOGNITION,
+                Manifest.permission.READ_MEDIA_AUDIO,
+                Manifest.permission.READ_MEDIA_IMAGES,
+                Manifest.permission.READ_MEDIA_VIDEO,
+            )
 
-        private val NEARBY_DEVICES_PERMISSIONS = indexedSetOf(
-            Manifest.permission.BLUETOOTH_ADVERTISE,
-            Manifest.permission.BLUETOOTH_CONNECT,
-            Manifest.permission.BLUETOOTH_SCAN,
-            Manifest.permission.NEARBY_WIFI_DEVICES
-        )
+        private val NEARBY_DEVICES_PERMISSIONS =
+            indexedSetOf(
+                Manifest.permission.BLUETOOTH_ADVERTISE,
+                Manifest.permission.BLUETOOTH_CONNECT,
+                Manifest.permission.BLUETOOTH_SCAN,
+                Manifest.permission.NEARBY_WIFI_DEVICES
+            )
 
-        private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(
-            Manifest.permission.POST_NOTIFICATIONS
-        )
+        private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(Manifest.permission.POST_NOTIFICATIONS)
 
-        private val STORAGE_AND_MEDIA_PERMISSIONS = indexedSetOf(
-            Manifest.permission.READ_EXTERNAL_STORAGE,
-            Manifest.permission.WRITE_EXTERNAL_STORAGE,
-            Manifest.permission.READ_MEDIA_AUDIO,
-            Manifest.permission.READ_MEDIA_VIDEO,
-            Manifest.permission.READ_MEDIA_IMAGES,
-            Manifest.permission.ACCESS_MEDIA_LOCATION,
-            Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
-        )
+        private val STORAGE_AND_MEDIA_PERMISSIONS =
+            indexedSetOf(
+                Manifest.permission.READ_EXTERNAL_STORAGE,
+                Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                Manifest.permission.READ_MEDIA_AUDIO,
+                Manifest.permission.READ_MEDIA_VIDEO,
+                Manifest.permission.READ_MEDIA_IMAGES,
+                Manifest.permission.ACCESS_MEDIA_LOCATION,
+                Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
+            )
 
-        /**
-         * Mask for all permission flags that can be set by the user
-         */
+        /** Mask for all permission flags that can be set by the user */
         private const val USER_SETTABLE_MASK =
             PermissionFlags.USER_SET or
                 PermissionFlags.USER_FIXED or
@@ -1558,16 +1728,14 @@
                 PermissionFlags.USER_SELECTED
 
         /**
-         * Mask for all permission flags that imply we shouldn't automatically modify the
-         * permission grant state.
+         * Mask for all permission flags that imply we shouldn't automatically modify the permission
+         * grant state.
          */
         private const val SYSTEM_OR_POLICY_FIXED_MASK =
             PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED
     }
 
-    /**
-     * Listener for permission flags changes.
-     */
+    /** Listener for permission flags changes. */
     abstract class OnPermissionFlagsChangedListener {
         /**
          * Called when a permission flags change has been made to the upcoming new state.
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
index b644d8f..edacda0 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
@@ -32,7 +32,6 @@
      * Upgrade the package permissions, if needed.
      *
      * @param version package version
-     *
      * @see [com.android.server.permission.access.util.PackageVersionMigration.getVersion]
      */
     fun MutateStateScope.upgradePackageState(
@@ -43,7 +42,8 @@
         val packageName = packageState.packageName
         if (version <= 3) {
             Slog.v(
-                LOG_TAG, "Allowlisting and upgrading background location permission for " +
+                LOG_TAG,
+                "Allowlisting and upgrading background location permission for " +
                     "package: $packageName, version: $version, user:$userId"
             )
             allowlistRestrictedPermissions(packageState, userId)
@@ -51,7 +51,8 @@
         }
         if (version <= 10) {
             Slog.v(
-                LOG_TAG, "Upgrading access media location permission for package: $packageName" +
+                LOG_TAG,
+                "Upgrading access media location permission for package: $packageName" +
                     ", version: $version, user: $userId"
             )
             upgradeAccessMediaLocationPermission(packageState, userId)
@@ -59,7 +60,8 @@
         // TODO Enable isAtLeastT check, when moving subsystem to mainline.
         if (version <= 12 /*&& SdkLevel.isAtLeastT()*/) {
             Slog.v(
-                LOG_TAG, "Upgrading scoped permissions for package: $packageName" +
+                LOG_TAG,
+                "Upgrading scoped permissions for package: $packageName" +
                     ", version: $version, user: $userId"
             )
             upgradeAuralVisualMediaPermissions(packageState, userId)
@@ -67,7 +69,8 @@
         // TODO Enable isAtLeastU check, when moving subsystem to mainline.
         if (version <= 14 /*&& SdkLevel.isAtLeastU()*/) {
             Slog.v(
-                LOG_TAG, "Upgrading visual media permission for package: $packageName" +
+                LOG_TAG,
+                "Upgrading visual media permission for package: $packageName" +
                     ", version: $version, user: $userId"
             )
             upgradeUserSelectedVisualMediaPermission(packageState, userId)
@@ -84,8 +87,11 @@
             if (permissionName in LEGACY_RESTRICTED_PERMISSIONS) {
                 with(policy) {
                     updatePermissionFlags(
-                        packageState.appId, userId, permissionName,
-                        PermissionFlags.UPGRADE_EXEMPT, PermissionFlags.UPGRADE_EXEMPT
+                        packageState.appId,
+                        userId,
+                        permissionName,
+                        PermissionFlags.UPGRADE_EXEMPT,
+                        PermissionFlags.UPGRADE_EXEMPT
                     )
                 }
             }
@@ -96,21 +102,27 @@
         packageState: PackageState,
         userId: Int
     ) {
-        if (Manifest.permission.ACCESS_BACKGROUND_LOCATION in
-            packageState.androidPackage!!.requestedPermissions) {
+        if (
+            Manifest.permission.ACCESS_BACKGROUND_LOCATION in
+                packageState.androidPackage!!.requestedPermissions
+        ) {
             val appId = packageState.appId
-            val accessFineLocationFlags = with(policy) {
-                getPermissionFlags(appId, userId, Manifest.permission.ACCESS_FINE_LOCATION)
-            }
-            val accessCoarseLocationFlags = with(policy) {
-                getPermissionFlags(appId, userId, Manifest.permission.ACCESS_COARSE_LOCATION)
-            }
+            val accessFineLocationFlags =
+                with(policy) {
+                    getPermissionFlags(appId, userId, Manifest.permission.ACCESS_FINE_LOCATION)
+                }
+            val accessCoarseLocationFlags =
+                with(policy) {
+                    getPermissionFlags(appId, userId, Manifest.permission.ACCESS_COARSE_LOCATION)
+                }
             val isForegroundLocationGranted =
                 PermissionFlags.isAppOpGranted(accessFineLocationFlags) ||
                     PermissionFlags.isAppOpGranted(accessCoarseLocationFlags)
             if (isForegroundLocationGranted) {
                 grantRuntimePermission(
-                    packageState, userId, Manifest.permission.ACCESS_BACKGROUND_LOCATION
+                    packageState,
+                    userId,
+                    Manifest.permission.ACCESS_BACKGROUND_LOCATION
                 )
             }
         }
@@ -120,24 +132,29 @@
         packageState: PackageState,
         userId: Int
     ) {
-        if (Manifest.permission.ACCESS_MEDIA_LOCATION in
-            packageState.androidPackage!!.requestedPermissions) {
-            val flags = with(policy) {
-                getPermissionFlags(
-                    packageState.appId, userId, Manifest.permission.READ_EXTERNAL_STORAGE
-                )
-            }
+        if (
+            Manifest.permission.ACCESS_MEDIA_LOCATION in
+                packageState.androidPackage!!.requestedPermissions
+        ) {
+            val flags =
+                with(policy) {
+                    getPermissionFlags(
+                        packageState.appId,
+                        userId,
+                        Manifest.permission.READ_EXTERNAL_STORAGE
+                    )
+                }
             if (PermissionFlags.isAppOpGranted(flags)) {
                 grantRuntimePermission(
-                    packageState, userId, Manifest.permission.ACCESS_MEDIA_LOCATION
+                    packageState,
+                    userId,
+                    Manifest.permission.ACCESS_MEDIA_LOCATION
                 )
             }
         }
     }
 
-    /**
-     * Upgrade permissions based on storage permissions grant
-     */
+    /** Upgrade permissions based on storage permissions grant */
     private fun MutateStateScope.upgradeAuralVisualMediaPermissions(
         packageState: PackageState,
         userId: Int
@@ -147,15 +164,15 @@
             return
         }
         val requestedPermissionNames = androidPackage.requestedPermissions
-        val isStorageUserGranted = STORAGE_PERMISSIONS.anyIndexed { _, permissionName ->
-            if (permissionName !in requestedPermissionNames) {
-                return@anyIndexed false
+        val isStorageUserGranted =
+            STORAGE_PERMISSIONS.anyIndexed { _, permissionName ->
+                if (permissionName !in requestedPermissionNames) {
+                    return@anyIndexed false
+                }
+                val flags =
+                    with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) }
+                PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET)
             }
-            val flags = with(policy) {
-                getPermissionFlags(packageState.appId, userId, permissionName)
-            }
-            PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET)
-        }
         if (isStorageUserGranted) {
             AURAL_VISUAL_MEDIA_PERMISSIONS.forEachIndexed { _, permissionName ->
                 if (permissionName in requestedPermissionNames) {
@@ -165,9 +182,7 @@
         }
     }
 
-    /**
-     * Upgrade permission based on the grant in [Manifest.permission_group.READ_MEDIA_VISUAL]
-     */
+    /** Upgrade permission based on the grant in [Manifest.permission_group.READ_MEDIA_VISUAL] */
     private fun MutateStateScope.upgradeUserSelectedVisualMediaPermission(
         packageState: PackageState,
         userId: Int
@@ -177,19 +192,21 @@
             return
         }
         val requestedPermissionNames = androidPackage.requestedPermissions
-        val isVisualMediaUserGranted = VISUAL_MEDIA_PERMISSIONS.anyIndexed { _, permissionName ->
-            if (permissionName !in requestedPermissionNames) {
-                return@anyIndexed false
+        val isVisualMediaUserGranted =
+            VISUAL_MEDIA_PERMISSIONS.anyIndexed { _, permissionName ->
+                if (permissionName !in requestedPermissionNames) {
+                    return@anyIndexed false
+                }
+                val flags =
+                    with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) }
+                PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET)
             }
-            val flags = with(policy) {
-                getPermissionFlags(packageState.appId, userId, permissionName)
-            }
-            PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET)
-        }
         if (isVisualMediaUserGranted) {
             if (Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED in requestedPermissionNames) {
                 grantRuntimePermission(
-                    packageState, userId, Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
+                    packageState,
+                    userId,
+                    Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
                 )
             }
         }
@@ -201,7 +218,8 @@
         permissionName: String
     ) {
         Slog.v(
-            LOG_TAG, "Granting runtime permission for package: ${packageState.packageName}, " +
+            LOG_TAG,
+            "Granting runtime permission for package: ${packageState.packageName}, " +
                 "permission: $permissionName, userId: $userId"
         )
         val permission = newState.systemState.permissions[permissionName]!!
@@ -220,13 +238,13 @@
         }
 
         flags = flags or PermissionFlags.RUNTIME_GRANTED
-        flags = flags andInv (
-            PermissionFlags.APP_OP_REVOKED or
-            PermissionFlags.IMPLICIT or
-            PermissionFlags.LEGACY_GRANTED or
-            PermissionFlags.HIBERNATION or
-            PermissionFlags.ONE_TIME
-        )
+        flags =
+            flags andInv
+                (PermissionFlags.APP_OP_REVOKED or
+                    PermissionFlags.IMPLICIT or
+                    PermissionFlags.LEGACY_GRANTED or
+                    PermissionFlags.HIBERNATION or
+                    PermissionFlags.ONE_TIME)
         with(policy) { setPermissionFlags(appId, userId, permissionName, flags) }
     }
 
@@ -234,39 +252,45 @@
         private val LOG_TAG = AppIdPermissionUpgrade::class.java.simpleName
 
         private const val MASK_ANY_FIXED =
-            PermissionFlags.USER_SET or PermissionFlags.USER_FIXED or
-            PermissionFlags.POLICY_FIXED or PermissionFlags.SYSTEM_FIXED
+            PermissionFlags.USER_SET or
+                PermissionFlags.USER_FIXED or
+                PermissionFlags.POLICY_FIXED or
+                PermissionFlags.SYSTEM_FIXED
 
-        private val LEGACY_RESTRICTED_PERMISSIONS = indexedSetOf(
-            Manifest.permission.ACCESS_BACKGROUND_LOCATION,
-            Manifest.permission.READ_EXTERNAL_STORAGE,
-            Manifest.permission.WRITE_EXTERNAL_STORAGE,
-            Manifest.permission.SEND_SMS,
-            Manifest.permission.RECEIVE_SMS,
-            Manifest.permission.RECEIVE_WAP_PUSH,
-            Manifest.permission.RECEIVE_MMS,
-            Manifest.permission.READ_CELL_BROADCASTS,
-            Manifest.permission.READ_CALL_LOG,
-            Manifest.permission.WRITE_CALL_LOG,
-            Manifest.permission.PROCESS_OUTGOING_CALLS
-        )
+        private val LEGACY_RESTRICTED_PERMISSIONS =
+            indexedSetOf(
+                Manifest.permission.ACCESS_BACKGROUND_LOCATION,
+                Manifest.permission.READ_EXTERNAL_STORAGE,
+                Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                Manifest.permission.SEND_SMS,
+                Manifest.permission.RECEIVE_SMS,
+                Manifest.permission.RECEIVE_WAP_PUSH,
+                Manifest.permission.RECEIVE_MMS,
+                Manifest.permission.READ_CELL_BROADCASTS,
+                Manifest.permission.READ_CALL_LOG,
+                Manifest.permission.WRITE_CALL_LOG,
+                Manifest.permission.PROCESS_OUTGOING_CALLS
+            )
 
-        private val STORAGE_PERMISSIONS = indexedSetOf(
-            Manifest.permission.READ_EXTERNAL_STORAGE,
-            Manifest.permission.WRITE_EXTERNAL_STORAGE
-        )
-        private val AURAL_VISUAL_MEDIA_PERMISSIONS = indexedSetOf(
-            Manifest.permission.READ_MEDIA_AUDIO,
-            Manifest.permission.READ_MEDIA_IMAGES,
-            Manifest.permission.READ_MEDIA_VIDEO,
-            Manifest.permission.ACCESS_MEDIA_LOCATION,
-            Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
-        )
+        private val STORAGE_PERMISSIONS =
+            indexedSetOf(
+                Manifest.permission.READ_EXTERNAL_STORAGE,
+                Manifest.permission.WRITE_EXTERNAL_STORAGE
+            )
+        private val AURAL_VISUAL_MEDIA_PERMISSIONS =
+            indexedSetOf(
+                Manifest.permission.READ_MEDIA_AUDIO,
+                Manifest.permission.READ_MEDIA_IMAGES,
+                Manifest.permission.READ_MEDIA_VIDEO,
+                Manifest.permission.ACCESS_MEDIA_LOCATION,
+                Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
+            )
         // Visual media permissions in T
-        private val VISUAL_MEDIA_PERMISSIONS = indexedSetOf(
-            Manifest.permission.READ_MEDIA_IMAGES,
-            Manifest.permission.READ_MEDIA_VIDEO,
-            Manifest.permission.ACCESS_MEDIA_LOCATION
-        )
+        private val VISUAL_MEDIA_PERMISSIONS =
+            indexedSetOf(
+                Manifest.permission.READ_MEDIA_IMAGES,
+                Manifest.permission.READ_MEDIA_VIDEO,
+                Manifest.permission.ACCESS_MEDIA_LOCATION
+            )
     }
 }
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt
index 37a4a90..1bee356 100644
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt
@@ -135,9 +135,7 @@
     ) {
         tag(TAG_DEVICE) {
             attributeInterned(ATTR_ID, deviceId)
-            permissionFlags.forEachIndexed { _, name, flags ->
-                serializePermission(name, flags)
-            }
+            permissionFlags.forEachIndexed { _, name, flags -> serializePermission(name, flags) }
         }
     }
 
@@ -145,11 +143,12 @@
         tag(TAG_PERMISSION) {
             attributeInterned(ATTR_NAME, name)
             // Never serialize one-time permissions as granted.
-            val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) {
-                flags andInv PermissionFlags.RUNTIME_GRANTED
-            } else {
-                flags
-            }
+            val serializedFlags =
+                if (flags.hasBits(PermissionFlags.ONE_TIME)) {
+                    flags andInv PermissionFlags.RUNTIME_GRANTED
+                } else {
+                    flags
+                }
             attributeInt(ATTR_FLAGS, serializedFlags)
         }
     }
@@ -166,4 +165,4 @@
         private const val ATTR_NAME = "name"
         private const val ATTR_FLAGS = "flags"
     }
-}
\ No newline at end of file
+}
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
index 4addab3..15a5859 100644
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
@@ -53,8 +53,8 @@
     override fun MutateStateScope.onAppIdRemoved(appId: Int) {
         newState.userStates.forEachIndexed { userStateIndex, _, userState ->
             if (appId in userState.appIdDevicePermissionFlags) {
-                newState.mutateUserStateAt(userStateIndex)
-                    .mutateAppIdDevicePermissionFlags() -= appId
+                newState.mutateUserStateAt(userStateIndex).mutateAppIdDevicePermissionFlags() -=
+                    appId
             }
         }
     }
@@ -96,10 +96,11 @@
         val appId = packageState.appId
         val appIdPermissionFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags
         androidPackage.requestedPermissions.forEach { permissionName ->
-            val isRequestedByOtherPackages = anyPackageInAppId(appId) {
-                it.packageName != packageName &&
-                    permissionName in it.androidPackage!!.requestedPermissions
-            }
+            val isRequestedByOtherPackages =
+                anyPackageInAppId(appId) {
+                    it.packageName != packageName &&
+                        permissionName in it.androidPackage!!.requestedPermissions
+                }
             if (isRequestedByOtherPackages) {
                 return@forEach
             }
@@ -116,7 +117,9 @@
         }
         newState.userStates.forEachIndexed { _, userId, userState ->
             userState.appIdDevicePermissionFlags[appId]?.forEachReversedIndexed {
-                    _, deviceId, permissionFlags ->
+                _,
+                deviceId,
+                permissionFlags ->
                 permissionFlags.forEachReversedIndexed { _, permissionName, _ ->
                     if (permissionName !in requestedPermissions) {
                         setPermissionFlags(appId, deviceId, userId, permissionName, 0)
@@ -166,11 +169,17 @@
         userId: Int,
         permissionName: String
     ): Int {
-        val flags = state.userStates[userId]?.appIdDevicePermissionFlags?.get(appId)?.get(deviceId)
-                ?.getWithDefault(permissionName, 0) ?: 0
+        val flags =
+            state.userStates[userId]
+                ?.appIdDevicePermissionFlags
+                ?.get(appId)
+                ?.get(deviceId)
+                ?.getWithDefault(permissionName, 0)
+                ?: 0
         if (PermissionManager.DEBUG_DEVICE_PERMISSIONS) {
             Slog.i(
-                LOG_TAG, "getPermissionFlags: appId=$appId, userId=$userId," +
+                LOG_TAG,
+                "getPermissionFlags: appId=$appId, userId=$userId," +
                     " deviceId=$deviceId, permissionName=$permissionName," +
                     " flags=${PermissionFlags.toString(flags)}"
             )
@@ -186,7 +195,12 @@
         flags: Int
     ): Boolean =
         updatePermissionFlags(
-            appId, deviceId, userId, permissionName, PermissionFlags.MASK_ALL, flags
+            appId,
+            deviceId,
+            userId,
+            permissionName,
+            PermissionFlags.MASK_ALL,
+            flags
         )
 
     private fun MutateStateScope.updatePermissionFlags(
@@ -201,20 +215,23 @@
             Slog.w(LOG_TAG, "$permissionName is not a device aware permission.")
             return false
         }
-        val oldFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags[appId]
-            ?.get(deviceId).getWithDefault(permissionName, 0)
+        val oldFlags =
+            newState.userStates[userId]!!
+                .appIdDevicePermissionFlags[appId]
+                ?.get(deviceId)
+                .getWithDefault(permissionName, 0)
         val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask)
         if (oldFlags == newFlags) {
             return false
         }
         val appIdDevicePermissionFlags =
             newState.mutateUserState(userId)!!.mutateAppIdDevicePermissionFlags()
-        val devicePermissionFlags = appIdDevicePermissionFlags.mutateOrPut(appId) {
-            MutableIndexedReferenceMap()
-        }
+        val devicePermissionFlags =
+            appIdDevicePermissionFlags.mutateOrPut(appId) { MutableIndexedReferenceMap() }
         if (PermissionManager.DEBUG_DEVICE_PERMISSIONS) {
             Slog.i(
-                LOG_TAG, "setPermissionFlags(): appId=$appId, userId=$userId," +
+                LOG_TAG,
+                "setPermissionFlags(): appId=$appId, userId=$userId," +
                     " deviceId=$deviceId, permissionName=$permissionName," +
                     " newFlags=${PermissionFlags.toString(newFlags)}"
             )
@@ -229,40 +246,39 @@
         }
         listeners.forEachIndexed { _, it ->
             it.onDevicePermissionFlagsChanged(
-                appId, userId, deviceId, permissionName, oldFlags, newFlags
+                appId,
+                userId,
+                deviceId,
+                permissionName,
+                oldFlags,
+                newFlags
             )
         }
         return true
     }
 
     fun addOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) {
-        synchronized(listenersLock) {
-            listeners = listeners + listener
-        }
+        synchronized(listenersLock) { listeners = listeners + listener }
     }
 
     fun removeOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) {
-        synchronized(listenersLock) {
-            listeners = listeners - listener
-        }
+        synchronized(listenersLock) { listeners = listeners - listener }
     }
 
     private fun isDeviceAwarePermission(permissionName: String): Boolean =
-            DEVICE_AWARE_PERMISSIONS.contains(permissionName)
+        DEVICE_AWARE_PERMISSIONS.contains(permissionName)
 
     companion object {
         private val LOG_TAG = DevicePermissionPolicy::class.java.simpleName
 
-        /**
-         * These permissions are supported for virtual devices.
-         */
+        /** These permissions are supported for virtual devices. */
         // TODO: b/298661870 - Use new API to get the list of device aware permissions.
         val DEVICE_AWARE_PERMISSIONS = emptySet<String>()
     }
 
     /**
-     * TODO: b/289355341 - implement listener for permission changes
-     * Listener for permission flags changes.
+     * TODO: b/289355341 - implement listener for permission changes Listener for permission flags
+     *   changes.
      */
     abstract class OnDevicePermissionFlagsChangedListener {
         /**
@@ -288,4 +304,4 @@
          */
         abstract fun onStateMutated()
     }
-}
\ No newline at end of file
+}
diff --git a/services/permission/java/com/android/server/permission/access/permission/Permission.kt b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
index c7fe1a9..aa56928 100644
--- a/services/permission/java/com/android/server/permission/access/permission/Permission.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
@@ -26,8 +26,7 @@
     val isReconciled: Boolean,
     val type: Int,
     val appId: Int,
-    @Suppress("ArrayInDataClass")
-    val gids: IntArray = EmptyArray.INT,
+    @Suppress("ArrayInDataClass") val gids: IntArray = EmptyArray.INT,
     val areGidsPerUser: Boolean = false
 ) {
     inline val name: String
@@ -43,8 +42,7 @@
         get() = type == TYPE_DYNAMIC
 
     inline val protectionLevel: Int
-        @Suppress("DEPRECATION")
-        get() = permissionInfo.protectionLevel
+        @Suppress("DEPRECATION") get() = permissionInfo.protectionLevel
 
     inline val protection: Int
         get() = permissionInfo.protection
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
index 550d148..b9d89c2 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
@@ -32,15 +32,12 @@
  *
  * The old binary permission state is now tracked by multiple `*_GRANTED` and `*_REVOKED` flags, so
  * that:
- *
  * - With [INSTALL_GRANTED] and [INSTALL_REVOKED], we can now get rid of the old per-package
  *   `areInstallPermissionsFixed` attribute and correctly track it per-permission, finally fixing
  *   edge cases during module rollbacks.
- *
  * - With [LEGACY_GRANTED] and [IMPLICIT_GRANTED], we can now ensure that legacy permissions and
  *   implicit permissions split from non-runtime permissions are never revoked, without checking
  *   split permissions and package state everywhere slowly and in slightly different ways.
- *
  * - With [RESTRICTION_REVOKED], we can now get rid of the error-prone logic about revoking and
  *   potentially re-granting permissions upon restriction state changes.
  *
@@ -55,9 +52,7 @@
  * don't have any effect on the binary permission state.
  */
 object PermissionFlags {
-    /**
-     * Permission flag for a normal permission that is granted at package installation.
-     */
+    /** Permission flag for a normal permission that is granted at package installation. */
     const val INSTALL_GRANTED = 1 shl 0
 
     /**
@@ -97,8 +92,8 @@
     /**
      * Permission flag for a runtime permission whose state is set by the user.
      *
-     * For example, this flag may be set when the permission is allowed by the user in the
-     * request permission dialog, or managed in the permission settings.
+     * For example, this flag may be set when the permission is allowed by the user in the request
+     * permission dialog, or managed in the permission settings.
      *
      * @see PackageManager.FLAG_PERMISSION_USER_SET
      */
@@ -290,8 +285,8 @@
     /**
      * Permission flag for a runtime permission that is selected by the user.
      *
-     * For example, this flag may be set when one of the coarse/fine location accuracies is
-     * selected by the user.
+     * For example, this flag may be set when one of the coarse/fine location accuracies is selected
+     * by the user.
      *
      * This flag is informational and managed by PermissionController.
      *
@@ -299,28 +294,37 @@
      */
     const val USER_SELECTED = 1 shl 23
 
-    /**
-     * Mask for all permission flags.
-     */
+    /** Mask for all permission flags. */
     const val MASK_ALL = 0.inv()
 
-    /**
-     * Mask for all permission flags that may be applied to a runtime permission.
-     */
-    const val MASK_RUNTIME = ROLE or RUNTIME_GRANTED or USER_SET or USER_FIXED or POLICY_FIXED or
-        SYSTEM_FIXED or PREGRANT or LEGACY_GRANTED or IMPLICIT_GRANTED or IMPLICIT or
-        USER_SENSITIVE_WHEN_GRANTED or USER_SENSITIVE_WHEN_REVOKED or INSTALLER_EXEMPT or
-        SYSTEM_EXEMPT or UPGRADE_EXEMPT or RESTRICTION_REVOKED or SOFT_RESTRICTED or
-        APP_OP_REVOKED or ONE_TIME or HIBERNATION or USER_SELECTED
+    /** Mask for all permission flags that may be applied to a runtime permission. */
+    const val MASK_RUNTIME =
+        ROLE or
+            RUNTIME_GRANTED or
+            USER_SET or
+            USER_FIXED or
+            POLICY_FIXED or
+            SYSTEM_FIXED or
+            PREGRANT or
+            LEGACY_GRANTED or
+            IMPLICIT_GRANTED or
+            IMPLICIT or
+            USER_SENSITIVE_WHEN_GRANTED or
+            USER_SENSITIVE_WHEN_REVOKED or
+            INSTALLER_EXEMPT or
+            SYSTEM_EXEMPT or
+            UPGRADE_EXEMPT or
+            RESTRICTION_REVOKED or
+            SOFT_RESTRICTED or
+            APP_OP_REVOKED or
+            ONE_TIME or
+            HIBERNATION or
+            USER_SELECTED
 
-    /**
-     * Mask for all permission flags about permission exemption.
-     */
+    /** Mask for all permission flags about permission exemption. */
     const val MASK_EXEMPT = INSTALLER_EXEMPT or SYSTEM_EXEMPT or UPGRADE_EXEMPT
 
-    /**
-     * Mask for all permission flags about permission restriction.
-     */
+    /** Mask for all permission flags about permission restriction. */
     const val MASK_RESTRICTED = RESTRICTION_REVOKED or SOFT_RESTRICTED
 
     fun isPermissionGranted(flags: Int): Boolean {
@@ -363,11 +367,13 @@
             apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT
         }
         if (flags.hasBits(IMPLICIT)) {
-            apiFlags = apiFlags or if (flags.hasBits(LEGACY_GRANTED)) {
-                PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
-            } else {
-                PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED
-            }
+            apiFlags =
+                apiFlags or
+                    if (flags.hasBits(LEGACY_GRANTED)) {
+                        PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+                    } else {
+                        PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED
+                    }
         }
         if (flags.hasBits(USER_SENSITIVE_WHEN_GRANTED)) {
             apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
@@ -440,8 +446,10 @@
         }
         flags = flags or (oldFlags and LEGACY_GRANTED)
         flags = flags or (oldFlags and IMPLICIT_GRANTED)
-        if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) ||
-            apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED)) {
+        if (
+            apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) ||
+                apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED)
+        ) {
             flags = flags or IMPLICIT
         }
         if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)) {
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 2a29265..ab3d78c 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -41,10 +41,10 @@
 import android.os.ServiceManager
 import android.os.UserHandle
 import android.os.UserManager
-import android.permission.flags.Flags
 import android.permission.IOnPermissionsChangeListener
 import android.permission.PermissionControllerManager
 import android.permission.PermissionManager
+import android.permission.flags.Flags
 import android.provider.Settings
 import android.util.ArrayMap
 import android.util.ArraySet
@@ -88,28 +88,25 @@
 import com.android.server.pm.UserManagerService
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils
 import com.android.server.pm.permission.LegacyPermission
-import com.android.server.pm.permission.Permission as LegacyPermission2
 import com.android.server.pm.permission.LegacyPermissionSettings
 import com.android.server.pm.permission.LegacyPermissionState
+import com.android.server.pm.permission.Permission as LegacyPermission2
 import com.android.server.pm.permission.PermissionManagerServiceInterface
 import com.android.server.pm.permission.PermissionManagerServiceInternal
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.pkg.PackageState
 import com.android.server.policy.SoftRestrictedPermissionPolicy
-import libcore.util.EmptyArray
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.ExecutionException
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.TimeoutException
+import libcore.util.EmptyArray
 
-/**
- * Modern implementation of [PermissionManagerServiceInterface].
- */
-class PermissionService(
-    private val service: AccessCheckingService
-) : PermissionManagerServiceInterface {
+/** Modern implementation of [PermissionManagerServiceInterface]. */
+class PermissionService(private val service: AccessCheckingService) :
+    PermissionManagerServiceInterface {
     private val policy =
         service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy
 
@@ -131,8 +128,7 @@
     private lateinit var onPermissionFlagsChangedListener: OnPermissionFlagsChangedListener
 
     private val storageVolumeLock = Any()
-    @GuardedBy("storageVolumeLock")
-    private val mountedStorageVolumes = ArraySet<String?>()
+    @GuardedBy("storageVolumeLock") private val mountedStorageVolumes = ArraySet<String?>()
     @GuardedBy("storageVolumeLock")
     private val storageVolumePackageNames = ArrayMap<String?, MutableList<String>>()
 
@@ -144,8 +140,8 @@
      * A permission backup might contain apps that are not installed. In this case we delay the
      * restoration until the app is installed.
      *
-     * This array (`userId -> noDelayedBackupLeft`) is `true` for all the users where
-     * there is **no more** delayed backup left.
+     * This array (`userId -> noDelayedBackupLeft`) is `true` for all the users where there is **no
+     * more** delayed backup left.
      */
     private val isDelayedPermissionBackupFinished = SparseBooleanArray()
 
@@ -154,9 +150,10 @@
         packageManagerInternal = LocalServices.getService(PackageManagerInternal::class.java)
         packageManagerLocal =
             LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java)
-        platformCompat = IPlatformCompat.Stub.asInterface(
-            ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)
-        )
+        platformCompat =
+            IPlatformCompat.Stub.asInterface(
+                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)
+            )
         systemConfig = SystemConfig.getInstance()
         userManagerInternal = LocalServices.getService(UserManagerInternal::class.java)
         userManagerService = UserManagerService.getInstance()
@@ -166,8 +163,8 @@
         PackageManager.invalidatePackageInfoCache()
         PermissionManager.disablePackageNamePermissionCache()
 
-        handlerThread = ServiceThread(LOG_TAG, Process.THREAD_PRIORITY_BACKGROUND, true)
-            .apply { start() }
+        handlerThread =
+            ServiceThread(LOG_TAG, Process.THREAD_PRIORITY_BACKGROUND, true).apply { start() }
         handler = Handler(handlerThread.looper)
         onPermissionsChangeListeners = OnPermissionsChangeListeners(FgThread.get().looper)
         onPermissionFlagsChangedListener = OnPermissionFlagsChangedListener()
@@ -181,9 +178,7 @@
                 return emptyList()
             }
 
-            val permissionGroups = service.getState {
-                with(policy) { getPermissionGroups() }
-            }
+            val permissionGroups = service.getState { with(policy) { getPermissionGroups() } }
 
             return permissionGroups.mapNotNullIndexedTo(ArrayList()) { _, _, permissionGroup ->
                 if (snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) {
@@ -206,9 +201,9 @@
                 return null
             }
 
-            permissionGroup = service.getState {
-                with(policy) { getPermissionGroups()[permissionGroupName] }
-            } ?: return null
+            permissionGroup =
+                service.getState { with(policy) { getPermissionGroups()[permissionGroupName] } }
+                    ?: return null
 
             if (!snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) {
                 return null
@@ -242,29 +237,28 @@
                 return null
             }
 
-            permission = service.getState {
-                with(policy) { getPermissions()[permissionName] }
-            } ?: return null
+            permission =
+                service.getState { with(policy) { getPermissions()[permissionName] } }
+                    ?: return null
 
             if (!snapshot.isPackageVisibleToUid(permission.packageName, callingUid)) {
                 return null
             }
 
             val opPackage = snapshot.getPackageState(opPackageName)?.androidPackage
-            targetSdkVersion = when {
-                // System sees all flags.
-                isRootOrSystemOrShellUid(callingUid) -> Build.VERSION_CODES.CUR_DEVELOPMENT
-                opPackage != null -> opPackage.targetSdkVersion
-                else -> Build.VERSION_CODES.CUR_DEVELOPMENT
-            }
+            targetSdkVersion =
+                when {
+                    // System sees all flags.
+                    isRootOrSystemOrShellUid(callingUid) -> Build.VERSION_CODES.CUR_DEVELOPMENT
+                    opPackage != null -> opPackage.targetSdkVersion
+                    else -> Build.VERSION_CODES.CUR_DEVELOPMENT
+                }
         }
 
         return permission.generatePermissionInfo(flags, targetSdkVersion)
     }
 
-    /**
-     * Generate a new [PermissionInfo] from [Permission] and adjust it accordingly.
-     */
+    /** Generate a new [PermissionInfo] from [Permission] and adjust it accordingly. */
     private fun Permission.generatePermissionInfo(
         flags: Int,
         targetSdkVersion: Int = Build.VERSION_CODES.CUR_DEVELOPMENT
@@ -296,22 +290,27 @@
                 return null
             }
 
-            val permissions = service.getState {
-                if (permissionGroupName != null) {
-                    val permissionGroup =
-                        with(policy) { getPermissionGroups()[permissionGroupName] } ?: return null
+            val permissions =
+                service.getState {
+                    if (permissionGroupName != null) {
+                        val permissionGroup =
+                            with(policy) { getPermissionGroups()[permissionGroupName] }
+                                ?: return null
 
-                    if (!snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) {
-                        return null
+                        if (
+                            !snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)
+                        ) {
+                            return null
+                        }
                     }
+
+                    with(policy) { getPermissions() }
                 }
 
-                with(policy) { getPermissions() }
-            }
-
             return permissions.mapNotNullIndexedTo(ArrayList()) { _, _, permission ->
-                if (permission.groupName == permissionGroupName &&
-                    snapshot.isPackageVisibleToUid(permission.packageName, callingUid)
+                if (
+                    permission.groupName == permissionGroupName &&
+                        snapshot.isPackageVisibleToUid(permission.packageName, callingUid)
                 ) {
                     permission.generatePermissionInfo(flags)
                 } else {
@@ -334,9 +333,7 @@
     private inline fun getPermissionsWithProtectionOrProtectionFlags(
         predicate: (Permission) -> Boolean
     ): List<PermissionInfo> {
-        val permissions = service.getState {
-            with(policy) { getPermissions() }
-        }
+        val permissions = service.getState { with(policy) { getPermissions() } }
 
         return permissions.mapNotNullIndexedTo(ArrayList()) { _, _, permission ->
             if (predicate(permission)) {
@@ -348,18 +345,16 @@
     }
 
     override fun getPermissionGids(permissionName: String, userId: Int): IntArray {
-        val permission = service.getState {
-            with(policy) { getPermissions()[permissionName] }
-        } ?: return EmptyArray.INT
+        val permission =
+            service.getState { with(policy) { getPermissions()[permissionName] } }
+                ?: return EmptyArray.INT
         return permission.getGidsForUser(userId)
     }
 
     override fun getInstalledPermissions(packageName: String): Set<String> {
         requireNotNull(packageName) { "packageName cannot be null" }
 
-        val permissions = service.getState {
-            with(policy) { getPermissions() }
-        }
+        val permissions = service.getState { with(policy) { getPermissions() } }
 
         return permissions.mapNotNullIndexedTo(ArraySet()) { _, _, permission ->
             if (permission.packageName == packageName) {
@@ -398,9 +393,8 @@
             permissionInfo.protectionLevel =
                 PermissionInfo.fixProtectionLevel(permissionInfo.protectionLevel)
 
-            val newPermission = Permission(
-                permissionInfo, true, Permission.TYPE_DYNAMIC, permissionTree.appId
-            )
+            val newPermission =
+                Permission(permissionInfo, true, Permission.TYPE_DYNAMIC, permissionTree.appId)
 
             with(policy) { addPermission(newPermission, !async) }
         }
@@ -431,7 +425,7 @@
         val callingUid = Binder.getCallingUid()
         val permissionTree = with(policy) { findPermissionTree(permissionName) }
         if (permissionTree != null && permissionTree.appId == UserHandle.getAppId(callingUid)) {
-                return permissionTree
+            return permissionTree
         }
 
         throw SecurityException(
@@ -447,8 +441,9 @@
         // if that plus the size of 'info' would exceed our stated maximum.
         if (permissionTree.appId != Process.SYSTEM_UID) {
             val permissionTreeFootprint = calculatePermissionTreeFootprint(permissionTree)
-            if (permissionTreeFootprint + permissionInfo.calculateFootprint() >
-                MAX_PERMISSION_TREE_FOOTPRINT
+            if (
+                permissionTreeFootprint + permissionInfo.calculateFootprint() >
+                    MAX_PERMISSION_TREE_FOOTPRINT
             ) {
                 throw SecurityException("Permission tree size cap exceeded")
             }
@@ -483,14 +478,16 @@
                 packageManagerInternal.getPackageStateInternal(androidPackage.packageName)
             if (packageState == null) {
                 Slog.e(
-                    LOG_TAG, "checkUidPermission: PackageState not found for AndroidPackage" +
+                    LOG_TAG,
+                    "checkUidPermission: PackageState not found for AndroidPackage" +
                         " $androidPackage"
                 )
                 return PackageManager.PERMISSION_DENIED
             }
-            val isPermissionGranted = service.getState {
-                isPermissionGranted(packageState, userId, permissionName, deviceId)
-            }
+            val isPermissionGranted =
+                service.getState {
+                    isPermissionGranted(packageState, userId, permissionName, deviceId)
+                }
             return if (isPermissionGranted) {
                 PackageManager.PERMISSION_GRANTED
             } else {
@@ -505,9 +502,7 @@
         }
     }
 
-    /**
-     * Internal implementation that should only be called by [checkUidPermission].
-     */
+    /** Internal implementation that should only be called by [checkUidPermission]. */
     private fun isSystemUidPermissionGranted(uid: Int, permissionName: String): Boolean {
         val uidPermissions = systemConfig.systemPermissions[uid] ?: return false
         if (permissionName in uidPermissions) {
@@ -532,12 +527,14 @@
             return PackageManager.PERMISSION_DENIED
         }
 
-        val packageState = packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId)
-            .use { it.getPackageState(packageName) } ?: return PackageManager.PERMISSION_DENIED
+        val packageState =
+            packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId).use {
+                it.getPackageState(packageName)
+            }
+                ?: return PackageManager.PERMISSION_DENIED
 
-        val isPermissionGranted = service.getState {
-            isPermissionGranted(packageState, userId, permissionName, deviceId)
-        }
+        val isPermissionGranted =
+            service.getState { isPermissionGranted(packageState, userId, permissionName, deviceId) }
         return if (isPermissionGranted) {
             PackageManager.PERMISSION_GRANTED
         } else {
@@ -566,8 +563,15 @@
         }
 
         val fullerPermissionName = FULLER_PERMISSIONS[permissionName]
-        if (fullerPermissionName != null &&
-            isSinglePermissionGranted(appId, userId, isInstantApp, fullerPermissionName, deviceId)
+        if (
+            fullerPermissionName != null &&
+                isSinglePermissionGranted(
+                    appId,
+                    userId,
+                    isInstantApp,
+                    fullerPermissionName,
+                    deviceId
+                )
         ) {
             return true
         }
@@ -575,9 +579,7 @@
         return false
     }
 
-    /**
-     * Internal implementation that should only be called by [isPermissionGranted].
-     */
+    /** Internal implementation that should only be called by [isPermissionGranted]. */
     private fun GetStateScope.isSinglePermissionGranted(
         appId: Int,
         userId: Int,
@@ -604,20 +606,27 @@
         requireNotNull(packageName) { "packageName cannot be null" }
         Preconditions.checkArgumentNonnegative(userId, "userId")
 
-        val packageState = packageManagerLocal.withUnfilteredSnapshot()
-            .use { it.getPackageState(packageName) }
+        val packageState =
+            packageManagerLocal.withUnfilteredSnapshot().use { it.getPackageState(packageName) }
         if (packageState == null) {
             Slog.w(LOG_TAG, "getGrantedPermissions: Unknown package $packageName")
             return emptySet()
         }
 
         service.getState {
-            val permissionFlags = with(policy) { getUidPermissionFlags(packageState.appId, userId) }
-                ?: return emptySet()
+            val permissionFlags =
+                with(policy) { getUidPermissionFlags(packageState.appId, userId) }
+                    ?: return emptySet()
 
             return permissionFlags.mapNotNullIndexedTo(ArraySet()) { _, permissionName, _ ->
-                if (isPermissionGranted(
-                        packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT)) {
+                if (
+                    isPermissionGranted(
+                        packageState,
+                        userId,
+                        permissionName,
+                        Context.DEVICE_ID_DEFAULT
+                    )
+                ) {
                     permissionName
                 } else {
                     null
@@ -635,8 +644,8 @@
             // permission state is not found, now we always return at least global GIDs. This is
             // more consistent with the pre-S-refactor behavior. This is also because we are now
             // actively trimming the per-UID objects when empty.
-            val permissionFlags = with(policy) { getUidPermissionFlags(appId, userId) }
-                ?: return globalGids.copyOf()
+            val permissionFlags =
+                with(policy) { getUidPermissionFlags(appId, userId) } ?: return globalGids.copyOf()
 
             val gids = GrowingIntArray.wrap(globalGids)
             permissionFlags.forEachIndexed { _, permissionName, flags ->
@@ -644,8 +653,8 @@
                     return@forEachIndexed
                 }
 
-                val permission = with(policy) { getPermissions()[permissionName] }
-                    ?: return@forEachIndexed
+                val permission =
+                    with(policy) { getPermissions()[permissionName] } ?: return@forEachIndexed
                 val permissionGids = permission.getGidsForUser(userId)
                 if (permissionGids.isEmpty()) {
                     return@forEachIndexed
@@ -662,9 +671,7 @@
         deviceId: Int,
         userId: Int
     ) {
-        setRuntimePermissionGranted(
-            packageName, userId, permissionName, deviceId, isGranted = true
-        )
+        setRuntimePermissionGranted(packageName, userId, permissionName, deviceId, isGranted = true)
     }
 
     override fun revokeRuntimePermission(
@@ -675,7 +682,12 @@
         reason: String?
     ) {
         setRuntimePermissionGranted(
-            packageName, userId, permissionName, deviceId, isGranted = false, revokeReason = reason
+            packageName,
+            userId,
+            permissionName,
+            deviceId,
+            isGranted = false,
+            revokeReason = reason
         )
     }
 
@@ -684,8 +696,12 @@
         userId: Int
     ) {
         setRuntimePermissionGranted(
-            packageName, userId, Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT,
-            isGranted = false, skipKillUid = true
+            packageName,
+            userId,
+            Manifest.permission.POST_NOTIFICATIONS,
+            Context.DEVICE_ID_DEFAULT,
+            isGranted = false,
+            skipKillUid = true
         )
     }
 
@@ -704,19 +720,24 @@
     ) {
         val methodName = if (isGranted) "grantRuntimePermission" else "revokeRuntimePermission"
         val callingUid = Binder.getCallingUid()
-        val isDebugEnabled = if (isGranted) {
-            PermissionManager.DEBUG_TRACE_GRANTS
-        } else {
-            PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES
-        }
-        if (isDebugEnabled &&
-            PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) {
+        val isDebugEnabled =
+            if (isGranted) {
+                PermissionManager.DEBUG_TRACE_GRANTS
+            } else {
+                PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES
+            }
+        if (
+            isDebugEnabled &&
+                PermissionManager.shouldTraceGrant(packageName, permissionName, userId)
+        ) {
             val callingUidName = packageManagerInternal.getNameForUid(callingUid)
             Slog.i(
-                LOG_TAG, "$methodName(packageName = $packageName," +
+                LOG_TAG,
+                "$methodName(packageName = $packageName," +
                     " permissionName = $permissionName" +
                     (if (isGranted) "" else "skipKillUid = $skipKillUid, reason = $revokeReason") +
-                    ", userId = $userId," + " callingUid = $callingUidName ($callingUid))",
+                    ", userId = $userId," +
+                    " callingUid = $callingUidName ($callingUid))",
                 RuntimeException()
             )
         }
@@ -727,23 +748,31 @@
         }
 
         enforceCallingOrSelfCrossUserPermission(
-            userId, enforceFullPermission = true, enforceShellRestriction = true, methodName
+            userId,
+            enforceFullPermission = true,
+            enforceShellRestriction = true,
+            methodName
         )
-        val enforcedPermissionName = if (isGranted) {
-            Manifest.permission.GRANT_RUNTIME_PERMISSIONS
-        } else {
-            Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
-        }
+        val enforcedPermissionName =
+            if (isGranted) {
+                Manifest.permission.GRANT_RUNTIME_PERMISSIONS
+            } else {
+                Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
+            }
         context.enforceCallingOrSelfPermission(enforcedPermissionName, methodName)
 
         val packageState: PackageState?
-        val permissionControllerPackageName = packageManagerInternal.getKnownPackageNames(
-            KnownPackages.PACKAGE_PERMISSION_CONTROLLER, UserHandle.USER_SYSTEM
-        ).first()
+        val permissionControllerPackageName =
+            packageManagerInternal
+                .getKnownPackageNames(
+                    KnownPackages.PACKAGE_PERMISSION_CONTROLLER,
+                    UserHandle.USER_SYSTEM
+                )
+                .first()
         val permissionControllerPackageState: PackageState?
         packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
-            packageState = snapshot.filtered(callingUid, userId)
-                .use { it.getPackageState(packageName) }
+            packageState =
+                snapshot.filtered(callingUid, userId).use { it.getPackageState(packageName) }
             permissionControllerPackageState =
                 snapshot.getPackageState(permissionControllerPackageName)
         }
@@ -756,11 +785,13 @@
             return
         }
 
-        val canManageRolePermission = isRootOrSystemUid(callingUid) ||
-            UserHandle.getAppId(callingUid) == permissionControllerPackageState!!.appId
-        val overridePolicyFixed = context.checkCallingOrSelfPermission(
-            Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY
-        ) == PackageManager.PERMISSION_GRANTED
+        val canManageRolePermission =
+            isRootOrSystemUid(callingUid) ||
+                UserHandle.getAppId(callingUid) == permissionControllerPackageState!!.appId
+        val overridePolicyFixed =
+            context.checkCallingOrSelfPermission(
+                Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY
+            ) == PackageManager.PERMISSION_GRANTED
 
         service.mutateState {
             with(onPermissionFlagsChangedListener) {
@@ -773,8 +804,15 @@
             }
 
             setRuntimePermissionGranted(
-                packageState, userId, permissionName, deviceId, isGranted, canManageRolePermission,
-                overridePolicyFixed, reportError = true, methodName
+                packageState,
+                userId,
+                permissionName,
+                deviceId,
+                isGranted,
+                canManageRolePermission,
+                overridePolicyFixed,
+                reportError = true,
+                methodName
             )
         }
     }
@@ -791,8 +829,9 @@
                     PackageInstaller.SessionParams.PERMISSION_STATE_DENIED -> {}
                     else -> {
                         Slog.w(
-                            LOG_TAG, "setRequestedPermissionStates: Unknown permission state" +
-                            " $permissionState for permission $permissionName"
+                            LOG_TAG,
+                            "setRequestedPermissionStates: Unknown permission state" +
+                                " $permissionState for permission $permissionName"
                         )
                         return@forEachIndexed
                     }
@@ -800,35 +839,50 @@
                 if (permissionName !in packageState.androidPackage!!.requestedPermissions) {
                     return@forEachIndexed
                 }
-                val permission = with(policy) { getPermissions()[permissionName] }
-                    ?: return@forEachIndexed
+                val permission =
+                    with(policy) { getPermissions()[permissionName] } ?: return@forEachIndexed
                 when {
                     permission.isDevelopment || permission.isRuntime -> {
-                        if (permissionState ==
-                            PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED) {
+                        if (
+                            permissionState ==
+                                PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED
+                        ) {
                             setRuntimePermissionGranted(
-                                packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT,
-                                isGranted = true, canManageRolePermission = false,
-                                overridePolicyFixed = false, reportError = false,
+                                packageState,
+                                userId,
+                                permissionName,
+                                Context.DEVICE_ID_DEFAULT,
+                                isGranted = true,
+                                canManageRolePermission = false,
+                                overridePolicyFixed = false,
+                                reportError = false,
                                 "setRequestedPermissionStates"
                             )
                             updatePermissionFlags(
-                                packageState.appId, userId, permissionName,
+                                packageState.appId,
+                                userId,
+                                permissionName,
                                 Context.DEVICE_ID_DEFAULT,
                                 PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED or
-                                PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0,
+                                    PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+                                0,
                                 canUpdateSystemFlags = false,
                                 reportErrorForUnknownPermission = false,
-                                isPermissionRequested = true, "setRequestedPermissionStates",
+                                isPermissionRequested = true,
+                                "setRequestedPermissionStates",
                                 packageState.packageName
                             )
                         }
                     }
-                    permission.isAppOp && permissionName in
+                    permission.isAppOp &&
+                        permissionName in
                             PackageInstallerService.INSTALLER_CHANGEABLE_APP_OP_PERMISSIONS ->
                         setAppOpPermissionGranted(
-                            packageState, userId, permissionName, permissionState ==
-                                    PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED
+                            packageState,
+                            userId,
+                            permissionName,
+                            permissionState ==
+                                PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED
                         )
                     else -> {}
                 }
@@ -836,9 +890,7 @@
         }
     }
 
-    /**
-     * Set whether a runtime permission is granted, without any validation on caller.
-     */
+    /** Set whether a runtime permission is granted, without any validation on caller. */
     private fun MutateStateScope.setRuntimePermissionGranted(
         packageState: PackageState,
         userId: Int,
@@ -876,8 +928,11 @@
                     // their permissions as always granted
                     return
                 }
-                if (isGranted && packageState.getUserStateOrDefault(userId).isInstantApp &&
-                    !permission.isInstant) {
+                if (
+                    isGranted &&
+                        packageState.getUserStateOrDefault(userId).isInstantApp &&
+                        !permission.isInstant
+                ) {
                     if (reportError) {
                         throw SecurityException(
                             "Cannot grant non-instant permission $permissionName to package" +
@@ -913,7 +968,8 @@
         if (oldFlags.hasBits(PermissionFlags.SYSTEM_FIXED)) {
             if (reportError) {
                 Slog.e(
-                    LOG_TAG, "$methodName: Cannot change system fixed permission $permissionName" +
+                    LOG_TAG,
+                    "$methodName: Cannot change system fixed permission $permissionName" +
                         " for package $packageName"
                 )
             }
@@ -923,7 +979,8 @@
         if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED) && !overridePolicyFixed) {
             if (reportError) {
                 Slog.e(
-                    LOG_TAG, "$methodName: Cannot change policy fixed permission $permissionName" +
+                    LOG_TAG,
+                    "$methodName: Cannot change policy fixed permission $permissionName" +
                         " for package $packageName"
                 )
             }
@@ -933,7 +990,8 @@
         if (isGranted && oldFlags.hasBits(PermissionFlags.RESTRICTION_REVOKED)) {
             if (reportError) {
                 Slog.e(
-                    LOG_TAG, "$methodName: Cannot grant hard-restricted non-exempt permission" +
+                    LOG_TAG,
+                    "$methodName: Cannot grant hard-restricted non-exempt permission" +
                         " $permissionName to package $packageName"
                 )
             }
@@ -942,14 +1000,19 @@
 
         if (isGranted && oldFlags.hasBits(PermissionFlags.SOFT_RESTRICTED)) {
             // TODO: Refactor SoftRestrictedPermissionPolicy.
-            val softRestrictedPermissionPolicy = SoftRestrictedPermissionPolicy.forPermission(
-                context, AndroidPackageUtils.generateAppInfoWithoutState(androidPackage),
-                androidPackage, UserHandle.of(userId), permissionName
-            )
+            val softRestrictedPermissionPolicy =
+                SoftRestrictedPermissionPolicy.forPermission(
+                    context,
+                    AndroidPackageUtils.generateAppInfoWithoutState(androidPackage),
+                    androidPackage,
+                    UserHandle.of(userId),
+                    permissionName
+                )
             if (!softRestrictedPermissionPolicy.mayGrantPermission()) {
                 if (reportError) {
                     Slog.e(
-                        LOG_TAG, "$methodName: Cannot grant soft-restricted non-exempt permission" +
+                        LOG_TAG,
+                        "$methodName: Cannot grant soft-restricted non-exempt permission" +
                             " $permissionName to package $packageName"
                     )
                 }
@@ -965,15 +1028,17 @@
         setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags)
 
         if (permission.isRuntime) {
-            val action = if (isGranted) {
-                MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED
-            } else {
-                MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED
-            }
-            val log = LogMaker(action).apply {
-                setPackageName(packageName)
-                addTaggedData(MetricsProto.MetricsEvent.FIELD_PERMISSION, permissionName)
-            }
+            val action =
+                if (isGranted) {
+                    MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED
+                } else {
+                    MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED
+                }
+            val log =
+                LogMaker(action).apply {
+                    setPackageName(packageName)
+                    addTaggedData(MetricsProto.MetricsEvent.FIELD_PERMISSION, permissionName)
+                }
             metricsLogger.write(log)
         }
     }
@@ -984,8 +1049,8 @@
         permissionName: String,
         isGranted: Boolean
     ) {
-        val appOpPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as
-            AppIdAppOpPolicy
+        val appOpPolicy =
+            service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy
         val appOpName = AppOpsManager.permissionToOp(permissionName)!!
         val mode = if (isGranted) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_ERRORED
         with(appOpPolicy) { setAppOpMode(packageState.appId, userId, appOpName, mode) }
@@ -1003,17 +1068,20 @@
         }
 
         enforceCallingOrSelfCrossUserPermission(
-            userId, enforceFullPermission = true, enforceShellRestriction = false,
+            userId,
+            enforceFullPermission = true,
+            enforceShellRestriction = false,
             "getPermissionFlags"
         )
         enforceCallingOrSelfAnyPermission(
-            "getPermissionFlags", Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+            "getPermissionFlags",
+            Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
             Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
             Manifest.permission.GET_RUNTIME_PERMISSIONS
         )
 
-        val packageState = packageManagerLocal.withFilteredSnapshot()
-            .use { it.getPackageState(packageName) }
+        val packageState =
+            packageManagerLocal.withFilteredSnapshot().use { it.getPackageState(packageName) }
         if (packageState == null) {
             Slog.w(LOG_TAG, "getPermissionFlags: Unknown package $packageName")
             return 0
@@ -1045,12 +1113,17 @@
         }
 
         enforceCallingOrSelfCrossUserPermission(
-            userId, enforceFullPermission = true, enforceShellRestriction = false,
+            userId,
+            enforceFullPermission = true,
+            enforceShellRestriction = false,
             "isPermissionRevokedByPolicy"
         )
 
-        val packageState = packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId)
-            .use { it.getPackageState(packageName) } ?: return false
+        val packageState =
+            packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId).use {
+                it.getPackageState(packageName)
+            }
+                ?: return false
 
         service.getState {
             if (isPermissionGranted(packageState, userId, permissionName, deviceId)) {
@@ -1069,12 +1142,13 @@
         // TODO(b/173235285): Some caller may pass USER_ALL as userId.
         // Preconditions.checkArgumentNonnegative(userId, "userId")
 
-        val packageState = packageManagerLocal.withUnfilteredSnapshot()
-            .use { it.getPackageState(packageName) } ?: return false
+        val packageState =
+            packageManagerLocal.withUnfilteredSnapshot().use { it.getPackageState(packageName) }
+                ?: return false
 
-        val permissionFlags = service.getState {
-            with(policy) { getUidPermissionFlags(packageState.appId, userId) }
-        } ?: return false
+        val permissionFlags =
+            service.getState { with(policy) { getUidPermissionFlags(packageState.appId, userId) } }
+                ?: return false
         return permissionFlags.anyIndexed { _, _, it -> it.hasBits(REVIEW_REQUIRED_FLAGS) }
     }
 
@@ -1090,13 +1164,18 @@
         }
 
         enforceCallingOrSelfCrossUserPermission(
-            userId, enforceFullPermission = true, enforceShellRestriction = false,
+            userId,
+            enforceFullPermission = true,
+            enforceShellRestriction = false,
             "shouldShowRequestPermissionRationale"
         )
 
         val callingUid = Binder.getCallingUid()
-        val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId)
-            .use { it.getPackageState(packageName) } ?: return false
+        val packageState =
+            packageManagerLocal.withFilteredSnapshot(callingUid, userId).use {
+                it.getPackageState(packageName)
+            }
+                ?: return false
         val appId = packageState.appId
         if (UserHandle.getAppId(callingUid) != appId) {
             return false
@@ -1115,17 +1194,24 @@
         }
 
         if (permissionName == Manifest.permission.ACCESS_BACKGROUND_LOCATION) {
-            val isBackgroundRationaleChangeEnabled = Binder::class.withClearedCallingIdentity {
-                try {
-                    platformCompat.isChangeEnabledByPackageName(
-                        BACKGROUND_RATIONALE_CHANGE_ID, packageName, userId
-                    )
-                } catch (e: RemoteException) {
-                    Slog.e(LOG_TAG, "shouldShowRequestPermissionRationale: Unable to check if" +
-                        " compatibility change is enabled", e)
-                    false
+            val isBackgroundRationaleChangeEnabled =
+                Binder::class.withClearedCallingIdentity {
+                    try {
+                        platformCompat.isChangeEnabledByPackageName(
+                            BACKGROUND_RATIONALE_CHANGE_ID,
+                            packageName,
+                            userId
+                        )
+                    } catch (e: RemoteException) {
+                        Slog.e(
+                            LOG_TAG,
+                            "shouldShowRequestPermissionRationale: Unable to check if" +
+                                " compatibility change is enabled",
+                            e
+                        )
+                        false
+                    }
                 }
-            }
             if (isBackgroundRationaleChangeEnabled) {
                 return true
             }
@@ -1144,20 +1230,30 @@
         userId: Int
     ) {
         val callingUid = Binder.getCallingUid()
-        if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES &&
-            PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) {
-            val flagMaskString = DebugUtils.flagsToString(
-                PackageManager::class.java, "FLAG_PERMISSION_", flagMask.toLong()
-            )
-            val flagValuesString = DebugUtils.flagsToString(
-                PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong()
-            )
+        if (
+            PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES &&
+                PermissionManager.shouldTraceGrant(packageName, permissionName, userId)
+        ) {
+            val flagMaskString =
+                DebugUtils.flagsToString(
+                    PackageManager::class.java,
+                    "FLAG_PERMISSION_",
+                    flagMask.toLong()
+                )
+            val flagValuesString =
+                DebugUtils.flagsToString(
+                    PackageManager::class.java,
+                    "FLAG_PERMISSION_",
+                    flagValues.toLong()
+                )
             val callingUidName = packageManagerInternal.getNameForUid(callingUid)
             Slog.i(
-                LOG_TAG, "updatePermissionFlags(packageName = $packageName," +
+                LOG_TAG,
+                "updatePermissionFlags(packageName = $packageName," +
                     " permissionName = $permissionName, flagMask = $flagMaskString," +
                     " flagValues = $flagValuesString, userId = $userId," +
-                    " callingUid = $callingUidName ($callingUid))", RuntimeException()
+                    " callingUid = $callingUidName ($callingUid))",
+                RuntimeException()
             )
         }
 
@@ -1167,11 +1263,14 @@
         }
 
         enforceCallingOrSelfCrossUserPermission(
-            userId, enforceFullPermission = true, enforceShellRestriction = true,
+            userId,
+            enforceFullPermission = true,
+            enforceShellRestriction = true,
             "updatePermissionFlags"
         )
         enforceCallingOrSelfAnyPermission(
-            "updatePermissionFlags", Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+            "updatePermissionFlags",
+            Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
             Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
         )
 
@@ -1208,8 +1307,10 @@
         // Different from the old implementation, which returns when package doesn't exist but
         // throws when package exists but isn't visible, we now return in both cases to avoid
         // leaking the package existence.
-        if (androidPackage == null ||
-            packageManagerInternal.filterAppAccess(packageName, callingUid, userId, false)) {
+        if (
+            androidPackage == null ||
+                packageManagerInternal.filterAppAccess(packageName, callingUid, userId, false)
+        ) {
             Slog.w(LOG_TAG, "updatePermissionFlags: Unknown package $packageName")
             return
         }
@@ -1219,26 +1320,35 @@
         // permissions.
         val canUpdateSystemFlags = isRootOrSystemUid(callingUid)
 
-        val isPermissionRequested = if (permissionName in androidPackage.requestedPermissions) {
-            // Fast path, the current package has requested the permission.
-            true
-        } else {
-            // Slow path, go through all shared user packages.
-            val sharedUserPackageNames =
-                packageManagerInternal.getSharedUserPackagesForPackage(packageName, userId)
-            sharedUserPackageNames.any { sharedUserPackageName ->
-                val sharedUserPackage = packageManagerInternal.getPackage(sharedUserPackageName)
-                sharedUserPackage != null &&
-                    permissionName in sharedUserPackage.requestedPermissions
+        val isPermissionRequested =
+            if (permissionName in androidPackage.requestedPermissions) {
+                // Fast path, the current package has requested the permission.
+                true
+            } else {
+                // Slow path, go through all shared user packages.
+                val sharedUserPackageNames =
+                    packageManagerInternal.getSharedUserPackagesForPackage(packageName, userId)
+                sharedUserPackageNames.any { sharedUserPackageName ->
+                    val sharedUserPackage = packageManagerInternal.getPackage(sharedUserPackageName)
+                    sharedUserPackage != null &&
+                        permissionName in sharedUserPackage.requestedPermissions
+                }
             }
-        }
 
         val appId = packageState.appId
         service.mutateState {
             updatePermissionFlags(
-                appId, userId, permissionName, deviceId, flagMask, flagValues, canUpdateSystemFlags,
-                reportErrorForUnknownPermission = true, isPermissionRequested,
-                "updatePermissionFlags", packageName
+                appId,
+                userId,
+                permissionName,
+                deviceId,
+                flagMask,
+                flagValues,
+                canUpdateSystemFlags,
+                reportErrorForUnknownPermission = true,
+                isPermissionRequested,
+                "updatePermissionFlags",
+                packageName
             )
         }
     }
@@ -1246,17 +1356,25 @@
     override fun updatePermissionFlagsForAllApps(flagMask: Int, flagValues: Int, userId: Int) {
         val callingUid = Binder.getCallingUid()
         if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES) {
-            val flagMaskString = DebugUtils.flagsToString(
-                PackageManager::class.java, "FLAG_PERMISSION_", flagMask.toLong()
-            )
-            val flagValuesString = DebugUtils.flagsToString(
-                PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong()
-            )
+            val flagMaskString =
+                DebugUtils.flagsToString(
+                    PackageManager::class.java,
+                    "FLAG_PERMISSION_",
+                    flagMask.toLong()
+                )
+            val flagValuesString =
+                DebugUtils.flagsToString(
+                    PackageManager::class.java,
+                    "FLAG_PERMISSION_",
+                    flagValues.toLong()
+                )
             val callingUidName = packageManagerInternal.getNameForUid(callingUid)
             Slog.i(
-                LOG_TAG, "updatePermissionFlagsForAllApps(flagMask = $flagMaskString," +
+                LOG_TAG,
+                "updatePermissionFlagsForAllApps(flagMask = $flagMaskString," +
                     " flagValues = $flagValuesString, userId = $userId," +
-                    " callingUid = $callingUidName ($callingUid))", RuntimeException()
+                    " callingUid = $callingUidName ($callingUid))",
+                RuntimeException()
             )
         }
 
@@ -1266,11 +1384,14 @@
         }
 
         enforceCallingOrSelfCrossUserPermission(
-            userId, enforceFullPermission = true, enforceShellRestriction = true,
+            userId,
+            enforceFullPermission = true,
+            enforceShellRestriction = true,
             "updatePermissionFlagsForAllApps"
         )
         enforceCallingOrSelfAnyPermission(
-            "updatePermissionFlagsForAllApps", Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+            "updatePermissionFlagsForAllApps",
+            Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
             Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
         )
 
@@ -1278,26 +1399,30 @@
         // flag, we now properly sanitize all flags as in updatePermissionFlags().
         val canUpdateSystemFlags = isRootOrSystemUid(callingUid)
 
-        val packageStates = packageManagerLocal.withUnfilteredSnapshot()
-            .use { it.packageStates }
+        val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { it.packageStates }
         service.mutateState {
             packageStates.forEach { (packageName, packageState) ->
                 val androidPackage = packageState.androidPackage ?: return@forEach
                 androidPackage.requestedPermissions.forEach { permissionName ->
                     updatePermissionFlags(
-                        packageState.appId, userId, permissionName, Context.DEVICE_ID_DEFAULT,
-                        flagMask, flagValues, canUpdateSystemFlags,
+                        packageState.appId,
+                        userId,
+                        permissionName,
+                        Context.DEVICE_ID_DEFAULT,
+                        flagMask,
+                        flagValues,
+                        canUpdateSystemFlags,
                         reportErrorForUnknownPermission = false,
-                        isPermissionRequested = true, "updatePermissionFlagsForAllApps", packageName
+                        isPermissionRequested = true,
+                        "updatePermissionFlagsForAllApps",
+                        packageName
                     )
                 }
             }
         }
     }
 
-    /**
-     * Update flags for a permission, without any validation on caller.
-     */
+    /** Update flags for a permission, without any validation on caller. */
     private fun MutateStateScope.updatePermissionFlags(
         appId: Int,
         userId: Int,
@@ -1311,20 +1436,19 @@
         methodName: String,
         packageName: String
     ) {
-        @Suppress("NAME_SHADOWING")
-        var flagMask = flagMask
-        @Suppress("NAME_SHADOWING")
-        var flagValues = flagValues
+        @Suppress("NAME_SHADOWING") var flagMask = flagMask
+        @Suppress("NAME_SHADOWING") var flagValues = flagValues
         // Only the system can change these flags and nothing else.
         if (!canUpdateSystemFlags) {
             // Different from the old implementation, which allowed non-system UIDs to remove (but
             // not add) permission restriction flags, we now consistently ignore them altogether.
-            val ignoredMask = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED or
-                PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT or
-                PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or
-                PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or
-                PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or
-                PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
+            val ignoredMask =
+                PackageManager.FLAG_PERMISSION_SYSTEM_FIXED or
+                    PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT or
+                    PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or
+                    PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or
+                    PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or
+                    PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
             flagMask = flagMask andInv ignoredMask
             flagValues = flagValues andInv ignoredMask
         }
@@ -1340,7 +1464,8 @@
         val oldFlags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId)
         if (!isPermissionRequested && oldFlags == 0) {
             Slog.w(
-                LOG_TAG, "$methodName: Permission $permissionName isn't requested by package" +
+                LOG_TAG,
+                "$methodName: Permission $permissionName isn't requested by package" +
                     " $packageName"
             )
             return
@@ -1365,21 +1490,29 @@
         }
 
         enforceCallingOrSelfCrossUserPermission(
-            userId, enforceFullPermission = false, enforceShellRestriction = false,
+            userId,
+            enforceFullPermission = false,
+            enforceShellRestriction = false,
             "getAllowlistedRestrictedPermissions"
         )
 
         val callingUid = Binder.getCallingUid()
-        val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId)
-            .use { it.getPackageState(packageName) } ?: return null
+        val packageState =
+            packageManagerLocal.withFilteredSnapshot(callingUid, userId).use {
+                it.getPackageState(packageName)
+            }
+                ?: return null
         val androidPackage = packageState.androidPackage ?: return null
 
-        val isCallerPrivileged = context.checkCallingOrSelfPermission(
-            Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
-        ) == PackageManager.PERMISSION_GRANTED
+        val isCallerPrivileged =
+            context.checkCallingOrSelfPermission(
+                Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
+            ) == PackageManager.PERMISSION_GRANTED
 
-        if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) &&
-            !isCallerPrivileged) {
+        if (
+            allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) &&
+                !isCallerPrivileged
+        ) {
             throw SecurityException(
                 "Querying system allowlist requires " +
                     Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
@@ -1389,8 +1522,12 @@
         val isCallerInstallerOnRecord =
             packageManagerInternal.isCallerInstallerOfRecord(androidPackage, callingUid)
 
-        if (allowlistedFlags.hasAnyBit(PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or
-                PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) {
+        if (
+            allowlistedFlags.hasAnyBit(
+                PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or
+                    PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
+            )
+        ) {
             if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
                 throw SecurityException(
                     "Querying upgrade or installer allowlist requires being installer on record" +
@@ -1400,7 +1537,9 @@
         }
 
         return getAllowlistedRestrictedPermissionsUnchecked(
-            packageState.appId, allowlistedFlags, userId
+            packageState.appId,
+            allowlistedFlags,
+            userId
         )
     }
 
@@ -1414,8 +1553,11 @@
             with(policy) { getPermissionFlags(appId, userId, permissionName) }
         } else {
             if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) {
-                Slog.i(LOG_TAG, "$permissionName is not device aware permission, " +
-                        " get the flags for default device.")
+                Slog.i(
+                    LOG_TAG,
+                    "$permissionName is not device aware permission, " +
+                        " get the flags for default device."
+                )
                 return with(policy) { getPermissionFlags(appId, userId, permissionName) }
             }
             val virtualDeviceManagerInternal = virtualDeviceManagerInternal
@@ -1423,8 +1565,7 @@
                 Slog.e(LOG_TAG, "Virtual device manager service is not available.")
                 return 0
             }
-            val persistentDeviceId =
-                    virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId)
+            val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId)
             if (persistentDeviceId != null) {
                 with(devicePolicy) {
                     getPermissionFlags(appId, persistentDeviceId, userId, permissionName)
@@ -1444,13 +1585,14 @@
         flags: Int
     ): Boolean {
         return if (!Flags.deviceAwarePermissionApis() || deviceId == Context.DEVICE_ID_DEFAULT) {
-            with(policy) {
-               setPermissionFlags(appId, userId, permissionName, flags)
-            }
+            with(policy) { setPermissionFlags(appId, userId, permissionName, flags) }
         } else {
             if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) {
-                Slog.i(LOG_TAG, "$permissionName is not device aware permission, " +
-                        " set the flags for default device.")
+                Slog.i(
+                    LOG_TAG,
+                    "$permissionName is not device aware permission, " +
+                        " set the flags for default device."
+                )
                 return with(policy) { setPermissionFlags(appId, userId, permissionName, flags) }
             }
 
@@ -1459,8 +1601,7 @@
                 Slog.e(LOG_TAG, "Virtual device manager service is not available.")
                 return false
             }
-            val persistentDeviceId =
-                    virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId)
+            val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId)
             if (persistentDeviceId != null) {
                 with(devicePolicy) {
                     setPermissionFlags(appId, persistentDeviceId, userId, permissionName, flags)
@@ -1473,17 +1614,17 @@
     }
 
     /**
-     * This method does not enforce checks on the caller, should only be called after
-     * required checks.
+     * This method does not enforce checks on the caller, should only be called after required
+     * checks.
      */
     private fun getAllowlistedRestrictedPermissionsUnchecked(
         appId: Int,
         allowlistedFlags: Int,
         userId: Int
     ): ArrayList<String>? {
-        val permissionFlags = service.getState {
-            with(policy) { getUidPermissionFlags(appId, userId) }
-        } ?: return null
+        val permissionFlags =
+            service.getState { with(policy) { getUidPermissionFlags(appId, userId) } }
+                ?: return null
 
         var queryFlags = 0
         if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM)) {
@@ -1512,14 +1653,18 @@
             return false
         }
 
-        val permissionNames = getAllowlistedRestrictedPermissions(
-            packageName, allowlistedFlags, userId
-        ) ?: ArrayList(1)
+        val permissionNames =
+            getAllowlistedRestrictedPermissions(packageName, allowlistedFlags, userId)
+                ?: ArrayList(1)
 
         if (permissionName !in permissionNames) {
             permissionNames += permissionName
             return setAllowlistedRestrictedPermissions(
-                packageName, permissionNames, allowlistedFlags, userId, isAddingPermission = true
+                packageName,
+                permissionNames,
+                allowlistedFlags,
+                userId,
+                isAddingPermission = true
             )
         }
         return false
@@ -1531,14 +1676,22 @@
         permissionNames: List<String>,
         userId: Int
     ) {
-        val newPermissionNames = getAllowlistedRestrictedPermissionsUnchecked(appId,
-            PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, userId
-        )?.let {
-            ArraySet(permissionNames).apply { this += it }.toList()
-        } ?: permissionNames
+        val newPermissionNames =
+            getAllowlistedRestrictedPermissionsUnchecked(
+                    appId,
+                    PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER,
+                    userId
+                )
+                ?.let { ArraySet(permissionNames).apply { this += it }.toList() }
+                ?: permissionNames
 
-        setAllowlistedRestrictedPermissionsUnchecked(androidPackage, appId, newPermissionNames,
-            PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, userId)
+        setAllowlistedRestrictedPermissionsUnchecked(
+            androidPackage,
+            appId,
+            newPermissionNames,
+            PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER,
+            userId
+        )
     }
 
     override fun removeAllowlistedRestrictedPermission(
@@ -1552,13 +1705,17 @@
             return false
         }
 
-        val permissions = getAllowlistedRestrictedPermissions(
-            packageName, allowlistedFlags, userId
-        ) ?: return false
+        val permissions =
+            getAllowlistedRestrictedPermissions(packageName, allowlistedFlags, userId)
+                ?: return false
 
         if (permissions.remove(permissionName)) {
             return setAllowlistedRestrictedPermissions(
-                packageName, permissions, allowlistedFlags, userId, isAddingPermission = false
+                packageName,
+                permissions,
+                allowlistedFlags,
+                userId,
+                isAddingPermission = false
             )
         }
 
@@ -1572,16 +1729,22 @@
             return false
         }
 
-        if (packageManagerLocal.withFilteredSnapshot()
-                .use { it.getPackageState(permission.packageName) } == null) {
+        if (
+            packageManagerLocal.withFilteredSnapshot().use {
+                it.getPackageState(permission.packageName)
+            } == null
+        ) {
             return false
         }
 
         val isImmutablyRestrictedPermission =
             permission.isHardOrSoftRestricted && permission.isImmutablyRestricted
-        if (isImmutablyRestrictedPermission && context.checkCallingOrSelfPermission(
-                Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
-            ) != PackageManager.PERMISSION_GRANTED) {
+        if (
+            isImmutablyRestrictedPermission &&
+                context.checkCallingOrSelfPermission(
+                    Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
+                ) != PackageManager.PERMISSION_GRANTED
+        ) {
             throw SecurityException(
                 "Cannot modify allowlist of an immutably restricted permission: ${permission.name}"
             )
@@ -1599,13 +1762,16 @@
     ): Boolean {
         Preconditions.checkArgument(allowlistedFlags.countOneBits() == 1)
 
-        val isCallerPrivileged = context.checkCallingOrSelfPermission(
-            Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
-        ) == PackageManager.PERMISSION_GRANTED
+        val isCallerPrivileged =
+            context.checkCallingOrSelfPermission(
+                Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
+            ) == PackageManager.PERMISSION_GRANTED
 
         val callingUid = Binder.getCallingUid()
-        val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId)
-            .use { snapshot -> snapshot.packageStates[packageName] ?: return false }
+        val packageState =
+            packageManagerLocal.withFilteredSnapshot(callingUid, userId).use { snapshot ->
+                snapshot.packageStates[packageName] ?: return false
+            }
         val androidPackage = packageState.androidPackage ?: return false
 
         val isCallerInstallerOnRecord =
@@ -1627,15 +1793,19 @@
         }
 
         setAllowlistedRestrictedPermissionsUnchecked(
-            androidPackage, packageState.appId, permissionNames, allowlistedFlags, userId
+            androidPackage,
+            packageState.appId,
+            permissionNames,
+            allowlistedFlags,
+            userId
         )
 
         return true
     }
 
     /**
-     * This method does not enforce checks on the caller, should only be called after
-     * required checks.
+     * This method does not enforce checks on the caller, should only be called after required
+     * checks.
      */
     private fun setAllowlistedRestrictedPermissionsUnchecked(
         androidPackage: AndroidPackage,
@@ -1712,22 +1882,24 @@
                         }
                     }
 
-                    newFlags = if (permission.isHardRestricted && !isExempt) {
-                        newFlags or PermissionFlags.RESTRICTION_REVOKED
-                    } else {
-                        newFlags andInv PermissionFlags.RESTRICTION_REVOKED
-                    }
-                    newFlags = if (permission.isSoftRestricted && !isExempt) {
-                        newFlags or PermissionFlags.SOFT_RESTRICTED
-                    } else {
-                        newFlags andInv PermissionFlags.SOFT_RESTRICTED
-                    }
-                    mask = mask or PermissionFlags.RESTRICTION_REVOKED or
-                        PermissionFlags.SOFT_RESTRICTED
+                    newFlags =
+                        if (permission.isHardRestricted && !isExempt) {
+                            newFlags or PermissionFlags.RESTRICTION_REVOKED
+                        } else {
+                            newFlags andInv PermissionFlags.RESTRICTION_REVOKED
+                        }
+                    newFlags =
+                        if (permission.isSoftRestricted && !isExempt) {
+                            newFlags or PermissionFlags.SOFT_RESTRICTED
+                        } else {
+                            newFlags andInv PermissionFlags.SOFT_RESTRICTED
+                        }
+                    mask =
+                        mask or
+                            PermissionFlags.RESTRICTION_REVOKED or
+                            PermissionFlags.SOFT_RESTRICTED
 
-                    updatePermissionFlags(
-                        appId, userId, requestedPermission, mask, newFlags
-                    )
+                    updatePermissionFlags(appId, userId, requestedPermission, mask, newFlags)
                 }
             }
         }
@@ -1735,12 +1907,8 @@
 
     override fun resetRuntimePermissions(androidPackage: AndroidPackage, userId: Int) {
         service.mutateState {
-            with(policy) {
-                resetRuntimePermissions(androidPackage.packageName, userId)
-            }
-            with(devicePolicy) {
-                resetRuntimePermissions(androidPackage.packageName, userId)
-            }
+            with(policy) { resetRuntimePermissions(androidPackage.packageName, userId) }
+            with(devicePolicy) { resetRuntimePermissions(androidPackage.packageName, userId) }
         }
     }
 
@@ -1748,12 +1916,8 @@
         packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
             service.mutateState {
                 snapshot.packageStates.forEach { (_, packageState) ->
-                    with(policy) {
-                        resetRuntimePermissions(packageState.packageName, userId)
-                    }
-                    with(devicePolicy) {
-                        resetRuntimePermissions(packageState.packageName, userId)
-                    }
+                    with(policy) { resetRuntimePermissions(packageState.packageName, userId) }
+                    with(devicePolicy) { resetRuntimePermissions(packageState.packageName, userId) }
                 }
             }
         }
@@ -1761,7 +1925,8 @@
 
     override fun addOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) {
         context.enforceCallingOrSelfPermission(
-            Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS, "addOnPermissionsChangeListener"
+            Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS,
+            "addOnPermissionsChangeListener"
         )
 
         onPermissionsChangeListeners.addListener(listener)
@@ -1786,9 +1951,7 @@
         requireNotNull(permissionName) { "permissionName cannot be null" }
         val packageNames = ArraySet<String>()
 
-        val permission = service.getState {
-            with(policy) { getPermissions()[permissionName] }
-        }
+        val permission = service.getState { with(policy) { getPermissions()[permissionName] } }
         if (permission == null || !permission.isAppOp) {
             packageNames.toTypedArray()
         }
@@ -1814,8 +1977,8 @@
                 androidPackage.requestedPermissions.forEach requestedPermissions@{ permissionName ->
                     val permission = permissions[permissionName] ?: return@requestedPermissions
                     if (permission.isAppOp) {
-                        val packageNames = appOpPermissionPackageNames
-                            .getOrPut(permissionName) { ArraySet() }
+                        val packageNames =
+                            appOpPermissionPackageNames.getOrPut(permissionName) { ArraySet() }
                         packageNames += androidPackage.packageName
                     }
                 }
@@ -1828,14 +1991,18 @@
         Preconditions.checkArgumentNonnegative(userId, "userId cannot be null")
         val backup = CompletableFuture<ByteArray>()
         permissionControllerManager.getRuntimePermissionBackup(
-            UserHandle.of(userId), PermissionThread.getExecutor(), backup::complete
+            UserHandle.of(userId),
+            PermissionThread.getExecutor(),
+            backup::complete
         )
 
         return try {
             backup.get(BACKUP_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
         } catch (e: Exception) {
             when (e) {
-                is TimeoutException, is InterruptedException, is ExecutionException -> {
+                is TimeoutException,
+                is InterruptedException,
+                is ExecutionException -> {
                     Slog.e(LOG_TAG, "Cannot create permission backup for user $userId", e)
                     null
                 }
@@ -1852,7 +2019,8 @@
             isDelayedPermissionBackupFinished -= userId
         }
         permissionControllerManager.stageAndApplyRuntimePermissionsBackup(
-            backup, UserHandle.of(userId)
+            backup,
+            UserHandle.of(userId)
         )
     }
 
@@ -1866,7 +2034,9 @@
             }
         }
         permissionControllerManager.applyStagedRuntimePermissionBackup(
-            packageName, UserHandle.of(userId), PermissionThread.getExecutor()
+            packageName,
+            UserHandle.of(userId),
+            PermissionThread.getExecutor()
         ) { hasMoreBackup ->
             if (hasMoreBackup) {
                 return@applyStagedRuntimePermissionBackup
@@ -1913,21 +2083,15 @@
     ): IndexedMap<Int, MutableIndexedSet<String>> {
         val appIds = MutableIndexedSet<Int>()
 
-        val packageStates = packageManagerLocal.withUnfilteredSnapshot().use {
-            it.packageStates
-        }
+        val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { it.packageStates }
         state.userStates.forEachIndexed { _, _, userState ->
-            userState.appIdPermissionFlags.forEachIndexed { _, appId, _ ->
-                appIds.add(appId)
-            }
-            userState.appIdAppOpModes.forEachIndexed { _, appId, _ ->
-                appIds.add(appId)
-            }
-            userState.packageVersions.forEachIndexed packageVersions@ { _, packageName, _ ->
+            userState.appIdPermissionFlags.forEachIndexed { _, appId, _ -> appIds.add(appId) }
+            userState.appIdAppOpModes.forEachIndexed { _, appId, _ -> appIds.add(appId) }
+            userState.packageVersions.forEachIndexed packageVersions@{ _, packageName, _ ->
                 val appId = packageStates[packageName]?.appId ?: return@packageVersions
                 appIds.add(appId)
             }
-            userState.packageAppOpModes.forEachIndexed packageAppOpModes@ { _, packageName, _ ->
+            userState.packageAppOpModes.forEachIndexed packageAppOpModes@{ _, packageName, _ ->
                 val appId = packageStates[packageName]?.appId ?: return@packageAppOpModes
                 appIds.add(appId)
             }
@@ -1935,7 +2099,8 @@
 
         val appIdPackageNames = MutableIndexedMap<Int, MutableIndexedSet<String>>()
         packageStates.forEach { (_, packageState) ->
-            appIdPackageNames.getOrPut(packageState.appId) { MutableIndexedSet() }
+            appIdPackageNames
+                .getOrPut(packageState.appId) { MutableIndexedSet() }
                 .add(packageState.packageName)
         }
         // add non-package app IDs which might not be reported by package manager.
@@ -1966,10 +2131,7 @@
         println("Permission groups:")
         withIndent {
             state.systemState.permissionGroups.forEachIndexed { _, _, permissionGroup ->
-                println(
-                    "${permissionGroup.name}: " +
-                        "packageName=${permissionGroup.packageName}"
-                )
+                println("${permissionGroup.name}: " + "packageName=${permissionGroup.packageName}")
             }
         }
 
@@ -1998,7 +2160,9 @@
                     println("Permissions:")
                     withIndent {
                         userState.appIdPermissionFlags[appId]?.forEachIndexed {
-                            _, permissionName, flags ->
+                            _,
+                            permissionName,
+                            flags ->
                             val isGranted = PermissionFlags.isPermissionGranted(flags)
                             println(
                                 "$permissionName: granted=$isGranted, flags=" +
@@ -2008,7 +2172,9 @@
                     }
 
                     userState.appIdDevicePermissionFlags[appId]?.forEachIndexed {
-                            _, deviceId, devicePermissionFlags ->
+                        _,
+                        deviceId,
+                        devicePermissionFlags ->
                         println("Permissions (Device $deviceId):")
                         withIndent {
                             devicePermissionFlags.forEachIndexed { _, permissionName, flags ->
@@ -2023,7 +2189,8 @@
 
                     println("App ops:")
                     withIndent {
-                        userState.appIdAppOpModes[appId]?.forEachIndexed {_, appOpName, appOpMode ->
+                        userState.appIdAppOpModes[appId]?.forEachIndexed { _, appOpName, appOpMode
+                            ->
                             println("$appOpName: mode=${AppOpsManager.modeToName(appOpMode)}")
                         }
                     }
@@ -2035,7 +2202,9 @@
                             println("App ops:")
                             withIndent {
                                 userState.packageAppOpModes[packageName]?.forEachIndexed {
-                                        _, appOpName, appOpMode ->
+                                    _,
+                                    appOpName,
+                                    appOpMode ->
                                     val modeName = AppOpsManager.modeToName(appOpMode)
                                     println("$appOpName: mode=$modeName")
                                 }
@@ -2054,24 +2223,30 @@
     }
 
     override fun getPermissionTEMP(permissionName: String): LegacyPermission2? {
-        val permission = service.getState {
-            with(policy) { getPermissions()[permissionName] }
-        } ?: return null
+        val permission =
+            service.getState { with(policy) { getPermissions()[permissionName] } } ?: return null
 
         return LegacyPermission2(
-            permission.permissionInfo, permission.type, permission.isReconciled, permission.appId,
-            permission.gids, permission.areGidsPerUser
+            permission.permissionInfo,
+            permission.type,
+            permission.isReconciled,
+            permission.appId,
+            permission.gids,
+            permission.areGidsPerUser
         )
     }
 
     override fun getLegacyPermissions(): List<LegacyPermission> =
-        service.getState {
-            with(policy) { getPermissions() }
-        }.mapIndexedTo(ArrayList()) { _, _, permission ->
-            LegacyPermission(
-                permission.permissionInfo, permission.type, permission.appId, permission.gids
-            )
-        }
+        service
+            .getState { with(policy) { getPermissions() } }
+            .mapIndexedTo(ArrayList()) { _, _, permission ->
+                LegacyPermission(
+                    permission.permissionInfo,
+                    permission.type,
+                    permission.appId,
+                    permission.gids
+                )
+            }
 
     override fun readLegacyPermissionsTEMP(legacyPermissionSettings: LegacyPermissionSettings) {
         // Package settings has been read when this method is called.
@@ -2092,9 +2267,7 @@
     ): List<LegacyPermission> =
         permissions.mapIndexedTo(ArrayList()) { _, _, permission ->
             // We don't need to provide UID and GIDs, which are only retrieved when dumping.
-            LegacyPermission(
-                permission.permissionInfo, permission.type, 0, EmptyArray.INT
-            )
+            LegacyPermission(permission.permissionInfo, permission.type, 0, EmptyArray.INT)
         }
 
     override fun getLegacyPermissionState(appId: Int): LegacyPermissionState {
@@ -2103,17 +2276,18 @@
         service.getState {
             val permissions = with(policy) { getPermissions() }
             userIds.forEachIndexed { _, userId ->
-                val permissionFlags = with(policy) { getUidPermissionFlags(appId, userId) }
-                    ?: return@forEachIndexed
+                val permissionFlags =
+                    with(policy) { getUidPermissionFlags(appId, userId) } ?: return@forEachIndexed
 
                 permissionFlags.forEachIndexed permissionFlags@{ _, permissionName, flags ->
                     val permission = permissions[permissionName] ?: return@permissionFlags
-                    val legacyPermissionState = LegacyPermissionState.PermissionState(
-                        permissionName,
-                        permission.isRuntime,
-                        PermissionFlags.isPermissionGranted(flags),
-                        PermissionFlags.toApiFlags(flags)
-                    )
+                    val legacyPermissionState =
+                        LegacyPermissionState.PermissionState(
+                            permissionName,
+                            permission.isRuntime,
+                            PermissionFlags.isPermissionGranted(flags),
+                            PermissionFlags.toApiFlags(flags)
+                        )
                     legacyState.putPermissionState(legacyPermissionState, userId)
                 }
             }
@@ -2137,16 +2311,13 @@
     override fun onSystemReady() {
         service.onSystemReady()
         virtualDeviceManagerInternal =
-                LocalServices.getService(VirtualDeviceManagerInternal::class.java)
-        permissionControllerManager = PermissionControllerManager(
-            context, PermissionThread.getHandler()
-        )
+            LocalServices.getService(VirtualDeviceManagerInternal::class.java)
+        permissionControllerManager =
+            PermissionControllerManager(context, PermissionThread.getHandler())
     }
 
     override fun onUserCreated(userId: Int) {
-        withCorkedPackageInfoCache {
-            service.onUserAdded(userId)
-        }
+        withCorkedPackageInfoCache { service.onUserAdded(userId) }
     }
 
     override fun onUserRemoved(userId: Int) {
@@ -2176,9 +2347,8 @@
             // of onPackageAdded() and reuse this order in onStorageVolumeAdded(). We need the
             // packages to be iterated in onStorageVolumeAdded() in the same order so that the
             // ownership of permissions is consistent.
-            storageVolumePackageNames.getOrPut(packageState.volumeUuid) {
-                mutableListOf()
-            } += packageState.packageName
+            storageVolumePackageNames.getOrPut(packageState.volumeUuid) { mutableListOf() } +=
+                packageState.packageName
             if (packageState.volumeUuid !in mountedStorageVolumes) {
                 // Wait for the storage volume to be mounted and batch the state mutation there.
                 return
@@ -2218,23 +2388,26 @@
                 return
             }
         }
-        val userIds = if (userId == UserHandle.USER_ALL) {
-            userManagerService.userIdsIncludingPreCreated
-        } else {
-            intArrayOf(userId)
-        }
+        val userIds =
+            if (userId == UserHandle.USER_ALL) {
+                userManagerService.userIdsIncludingPreCreated
+            } else {
+                intArrayOf(userId)
+            }
         @Suppress("NAME_SHADOWING")
-        userIds.forEach { userId ->
-            service.onPackageInstalled(androidPackage.packageName, userId)
-        }
+        userIds.forEach { userId -> service.onPackageInstalled(androidPackage.packageName, userId) }
 
         @Suppress("NAME_SHADOWING")
         userIds.forEach { userId ->
             // TODO: Remove when this callback receives packageState directly.
             val packageState =
                 packageManagerInternal.getPackageStateInternal(androidPackage.packageName)!!
-            addAllowlistedRestrictedPermissionsUnchecked(androidPackage, packageState.appId,
-                params.allowlistedRestrictedPermissions, userId)
+            addAllowlistedRestrictedPermissionsUnchecked(
+                androidPackage,
+                packageState.appId,
+                params.allowlistedRestrictedPermissions,
+                userId
+            )
             setRequestedPermissionStates(packageState, userId, params.permissionStates)
         }
     }
@@ -2247,11 +2420,12 @@
         sharedUserPkgs: List<AndroidPackage>,
         userId: Int
     ) {
-        val userIds = if (userId == UserHandle.USER_ALL) {
-            userManagerService.userIdsIncludingPreCreated
-        } else {
-            intArrayOf(userId)
-        }
+        val userIds =
+            if (userId == UserHandle.USER_ALL) {
+                userManagerService.userIdsIncludingPreCreated
+            } else {
+                intArrayOf(userId)
+            }
         userIds.forEach { service.onPackageUninstalled(packageName, appId, it) }
         val packageState = packageManagerInternal.packageStates[packageName]
         if (packageState == null) {
@@ -2268,31 +2442,26 @@
         }
     }
 
-    /**
-     * Check whether a UID is root or system UID.
-     */
+    /** Check whether a UID is root or system UID. */
     private fun isRootOrSystemUid(uid: Int) =
         when (UserHandle.getAppId(uid)) {
-            Process.ROOT_UID, Process.SYSTEM_UID -> true
+            Process.ROOT_UID,
+            Process.SYSTEM_UID -> true
             else -> false
         }
 
-    /**
-     * Check whether a UID is shell UID.
-     */
+    /** Check whether a UID is shell UID. */
     private fun isShellUid(uid: Int) = UserHandle.getAppId(uid) == Process.SHELL_UID
 
-    /**
-     * Check whether a UID is root, system or shell UID.
-     */
+    /** Check whether a UID is root, system or shell UID. */
     private fun isRootOrSystemOrShellUid(uid: Int) = isRootOrSystemUid(uid) || isShellUid(uid)
 
     /**
      * This method should typically only be used when granting or revoking permissions, since the
      * app may immediately restart after this call.
      *
-     * If you're doing surgery on app code/data, use [PackageFreezer] to guard your work against
-     * the app being relaunched.
+     * If you're doing surgery on app code/data, use [PackageFreezer] to guard your work against the
+     * app being relaunched.
      */
     private fun killUid(uid: Int, reason: String) {
         val activityManager = ActivityManager.getService()
@@ -2309,9 +2478,7 @@
         }
     }
 
-    /**
-     * @see PackageManagerLocal.withFilteredSnapshot
-     */
+    /** @see PackageManagerLocal.withFilteredSnapshot */
     private fun PackageManagerLocal.withFilteredSnapshot(
         callingUid: Int,
         userId: Int
@@ -2329,35 +2496,27 @@
         packageName: String
     ): PackageState? = packageStates[packageName]
 
-    /**
-     * Check whether a UID belongs to an instant app.
-     */
+    /** Check whether a UID belongs to an instant app. */
     private fun PackageManagerLocal.UnfilteredSnapshot.isUidInstantApp(uid: Int): Boolean =
         // Unfortunately we don't have the API for getting the owner UID of an isolated UID or the
         // API for getting the SharedUserApi object for an app ID yet, so for now we just keep
         // calling the old API.
         packageManagerInternal.getInstantAppPackageName(uid) != null
 
-    /**
-     * Check whether a package is visible to a UID within the same user as the UID.
-     */
+    /** Check whether a package is visible to a UID within the same user as the UID. */
     private fun PackageManagerLocal.UnfilteredSnapshot.isPackageVisibleToUid(
         packageName: String,
         uid: Int
     ): Boolean = isPackageVisibleToUid(packageName, UserHandle.getUserId(uid), uid)
 
-    /**
-     * Check whether a package in a particular user is visible to a UID.
-     */
+    /** Check whether a package in a particular user is visible to a UID. */
     private fun PackageManagerLocal.UnfilteredSnapshot.isPackageVisibleToUid(
         packageName: String,
         userId: Int,
         uid: Int
     ): Boolean = filtered(uid, userId).use { it.getPackageState(packageName) != null }
 
-    /**
-     * @see PackageManagerLocal.UnfilteredSnapshot.filtered
-     */
+    /** @see PackageManagerLocal.UnfilteredSnapshot.filtered */
     private fun PackageManagerLocal.UnfilteredSnapshot.filtered(
         callingUid: Int,
         userId: Int
@@ -2380,13 +2539,16 @@
         val callingUid = Binder.getCallingUid()
         val callingUserId = UserHandle.getUserId(callingUid)
         if (userId != callingUserId) {
-            val permissionName = if (enforceFullPermission) {
-                Manifest.permission.INTERACT_ACROSS_USERS_FULL
-            } else {
-                Manifest.permission.INTERACT_ACROSS_USERS
-            }
-            if (context.checkCallingOrSelfPermission(permissionName) !=
-                PackageManager.PERMISSION_GRANTED) {
+            val permissionName =
+                if (enforceFullPermission) {
+                    Manifest.permission.INTERACT_ACROSS_USERS_FULL
+                } else {
+                    Manifest.permission.INTERACT_ACROSS_USERS
+                }
+            if (
+                context.checkCallingOrSelfPermission(permissionName) !=
+                    PackageManager.PERMISSION_GRANTED
+            ) {
                 val exceptionMessage = buildString {
                     if (message != null) {
                         append(message)
@@ -2403,9 +2565,11 @@
             }
         }
         if (enforceShellRestriction && isShellUid(callingUid)) {
-            val isShellRestricted = userManagerInternal.hasUserRestriction(
-                UserManager.DISALLOW_DEBUGGING_FEATURES, userId
-            )
+            val isShellRestricted =
+                userManagerInternal.hasUserRestriction(
+                    UserManager.DISALLOW_DEBUGGING_FEATURES,
+                    userId
+                )
             if (isShellRestricted) {
                 val exceptionMessage = buildString {
                     if (message != null) {
@@ -2430,10 +2594,11 @@
         message: String?,
         vararg permissionNames: String
     ) {
-        val hasAnyPermission = permissionNames.any { permissionName ->
-            context.checkCallingOrSelfPermission(permissionName) ==
-                PackageManager.PERMISSION_GRANTED
-        }
+        val hasAnyPermission =
+            permissionNames.any { permissionName ->
+                context.checkCallingOrSelfPermission(permissionName) ==
+                    PackageManager.PERMISSION_GRANTED
+            }
         if (!hasAnyPermission) {
             val exceptionMessage = buildString {
                 if (message != null) {
@@ -2449,9 +2614,7 @@
         }
     }
 
-    /**
-     * Callback invoked when interesting actions have been taken on a permission.
-     */
+    /** Callback invoked when interesting actions have been taken on a permission. */
     private inner class OnPermissionFlagsChangedListener :
         AppIdPermissionPolicy.OnPermissionFlagsChangedListener() {
         private var isPermissionFlagsChanged = false
@@ -2482,9 +2645,8 @@
             isPermissionFlagsChanged = true
 
             val uid = UserHandle.getUid(userId, appId)
-            val permission = service.getState {
-                with(policy) { getPermissions()[permissionName] }
-            } ?: return
+            val permission =
+                service.getState { with(policy) { getPermissions()[permissionName] } } ?: return
             val wasPermissionGranted = PermissionFlags.isPermissionGranted(oldFlags)
             val isPermissionGranted = PermissionFlags.isPermissionGranted(newFlags)
 
@@ -2518,16 +2680,20 @@
             runtimePermissionChangedUids.clear()
 
             if (!isKillRuntimePermissionRevokedUidsSkipped) {
-                val reason = if (killRuntimePermissionRevokedUidsReasons.isNotEmpty()) {
-                    killRuntimePermissionRevokedUidsReasons.joinToString(", ")
-                } else {
-                    PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED
-                }
+                val reason =
+                    if (killRuntimePermissionRevokedUidsReasons.isNotEmpty()) {
+                        killRuntimePermissionRevokedUidsReasons.joinToString(", ")
+                    } else {
+                        PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED
+                    }
                 runtimePermissionRevokedUids.forEachIndexed {
-                    _, uid, areOnlyNotificationsPermissionsRevoked ->
+                    _,
+                    uid,
+                    areOnlyNotificationsPermissionsRevoked ->
                     handler.post {
-                        if (areOnlyNotificationsPermissionsRevoked &&
-                            isAppBackupAndRestoreRunning(uid)
+                        if (
+                            areOnlyNotificationsPermissionsRevoked &&
+                                isAppBackupAndRestoreRunning(uid)
                         ) {
                             return@post
                         }
@@ -2547,19 +2713,27 @@
         }
 
         private fun isAppBackupAndRestoreRunning(uid: Int): Boolean {
-            if (checkUidPermission(uid, Manifest.permission.BACKUP, Context.DEVICE_ID_DEFAULT) !=
-                PackageManager.PERMISSION_GRANTED) {
+            if (
+                checkUidPermission(uid, Manifest.permission.BACKUP, Context.DEVICE_ID_DEFAULT) !=
+                    PackageManager.PERMISSION_GRANTED
+            ) {
                 return false
             }
             return try {
                 val contentResolver = context.contentResolver
                 val userId = UserHandle.getUserId(uid)
-                val isInSetup = Settings.Secure.getIntForUser(
-                    contentResolver, Settings.Secure.USER_SETUP_COMPLETE, userId
-                ) == 0
-                val isInDeferredSetup = Settings.Secure.getIntForUser(
-                    contentResolver, Settings.Secure.USER_SETUP_PERSONALIZATION_STATE, userId
-                ) == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED
+                val isInSetup =
+                    Settings.Secure.getIntForUser(
+                        contentResolver,
+                        Settings.Secure.USER_SETUP_COMPLETE,
+                        userId
+                    ) == 0
+                val isInDeferredSetup =
+                    Settings.Secure.getIntForUser(
+                        contentResolver,
+                        Settings.Secure.USER_SETUP_PERSONALIZATION_STATE,
+                        userId
+                    ) == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED
                 isInSetup || isInDeferredSetup
             } catch (e: Settings.SettingNotFoundException) {
                 Slog.w(LOG_TAG, "Failed to check if the user is in restore: $e")
@@ -2620,31 +2794,34 @@
         @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
         private val BACKGROUND_RATIONALE_CHANGE_ID = 147316723L
 
-        private val FULLER_PERMISSIONS = ArrayMap<String, String>().apply {
-            this[Manifest.permission.ACCESS_COARSE_LOCATION] =
-                Manifest.permission.ACCESS_FINE_LOCATION
-            this[Manifest.permission.INTERACT_ACROSS_USERS] =
-                Manifest.permission.INTERACT_ACROSS_USERS_FULL
-        }
+        private val FULLER_PERMISSIONS =
+            ArrayMap<String, String>().apply {
+                this[Manifest.permission.ACCESS_COARSE_LOCATION] =
+                    Manifest.permission.ACCESS_FINE_LOCATION
+                this[Manifest.permission.INTERACT_ACROSS_USERS] =
+                    Manifest.permission.INTERACT_ACROSS_USERS_FULL
+            }
 
-        private val NOTIFICATIONS_PERMISSIONS = arraySetOf(
-            Manifest.permission.POST_NOTIFICATIONS
-        )
+        private val NOTIFICATIONS_PERMISSIONS = arraySetOf(Manifest.permission.POST_NOTIFICATIONS)
 
-        private const val REVIEW_REQUIRED_FLAGS = PermissionFlags.LEGACY_GRANTED or
-            PermissionFlags.IMPLICIT
-        private const val UNREQUESTABLE_MASK = PermissionFlags.RESTRICTION_REVOKED or
-            PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED or
-            PermissionFlags.USER_FIXED
+        private const val REVIEW_REQUIRED_FLAGS =
+            PermissionFlags.LEGACY_GRANTED or PermissionFlags.IMPLICIT
+        private const val UNREQUESTABLE_MASK =
+            PermissionFlags.RESTRICTION_REVOKED or
+                PermissionFlags.SYSTEM_FIXED or
+                PermissionFlags.POLICY_FIXED or
+                PermissionFlags.USER_FIXED
 
         private val BACKUP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60)
 
-        /** Cap the size of permission trees that 3rd party apps can define; in characters of text  */
+        /**
+         * Cap the size of permission trees that 3rd party apps can define; in characters of text
+         */
         private const val MAX_PERMISSION_TREE_FOOTPRINT = 32768
 
         private const val PERMISSION_ALLOWLIST_MASK =
             PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or
-            PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or
-            PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
+                PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or
+                PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
     }
 }
diff --git a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
index bd82935..6b20ef1 100644
--- a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
@@ -25,9 +25,7 @@
 import java.io.FileOutputStream
 import java.io.IOException
 
-/**
- * Read from an [AtomicFile], fallback to reserve file to read the data.
- */
+/** Read from an [AtomicFile], fallback to reserve file to read the data. */
 @Throws(Exception::class)
 inline fun AtomicFile.readWithReserveCopy(block: (FileInputStream) -> Unit) {
     try {
@@ -46,9 +44,7 @@
     }
 }
 
-/**
- * Write to actual file and reserve file.
- */
+/** Write to actual file and reserve file. */
 @Throws(IOException::class)
 inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) {
     val reserveFile = File(baseFile.parentFile, baseFile.name + ".reservecopy")
@@ -66,9 +62,7 @@
     }
 }
 
-/**
- * Write to an [AtomicFile] and close everything safely when done.
- */
+/** Write to an [AtomicFile] and close everything safely when done. */
 @Throws(IOException::class)
 // Renamed to writeInlined() to avoid conflict with the hidden AtomicFile.write() that isn't inline.
 inline fun AtomicFile.writeInlined(block: (FileOutputStream) -> Unit) {
diff --git a/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt b/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt
index 1d27aef..6ab73c7 100644
--- a/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt
@@ -22,9 +22,7 @@
 import org.xmlpull.v1.XmlPullParser
 import org.xmlpull.v1.XmlPullParserException
 
-/**
- * Parse content from [InputStream] with [BinaryXmlPullParser].
- */
+/** Parse content from [InputStream] with [BinaryXmlPullParser]. */
 @Throws(IOException::class, XmlPullParserException::class)
 inline fun InputStream.parseBinaryXml(block: BinaryXmlPullParser.() -> Unit) {
     BinaryXmlPullParser().apply {
@@ -35,6 +33,7 @@
 
 /**
  * Iterate through child tags of the current tag.
+ *
  * <p>
  * Attributes for the current tag needs to be accessed before this method is called because this
  * method will advance the parser past the start tag of the current tag. The code inspecting each
@@ -50,7 +49,8 @@
 inline fun BinaryXmlPullParser.forEachTag(block: BinaryXmlPullParser.() -> Unit) {
     when (val eventType = eventType) {
         // Document start or start tag of the parent tag.
-        XmlPullParser.START_DOCUMENT, XmlPullParser.START_TAG -> nextTagOrEnd()
+        XmlPullParser.START_DOCUMENT,
+        XmlPullParser.START_TAG -> nextTagOrEnd()
         else -> throw XmlPullParserException("Unexpected event type $eventType")
     }
     while (true) {
@@ -90,7 +90,8 @@
                 nextTagOrEnd()
             }
             // End tag of the parent tag, or document end.
-            XmlPullParser.END_TAG, XmlPullParser.END_DOCUMENT -> break
+            XmlPullParser.END_TAG,
+            XmlPullParser.END_DOCUMENT -> break
             else -> throw XmlPullParserException("Unexpected event type $eventType")
         }
     }
@@ -107,193 +108,146 @@
 inline fun BinaryXmlPullParser.nextTagOrEnd(): Int {
     while (true) {
         when (val eventType = next()) {
-            XmlPullParser.START_TAG, XmlPullParser.END_TAG, XmlPullParser.END_DOCUMENT ->
-                return eventType
+            XmlPullParser.START_TAG,
+            XmlPullParser.END_TAG,
+            XmlPullParser.END_DOCUMENT -> return eventType
             else -> continue
         }
     }
 }
 
-/**
- * @see BinaryXmlPullParser.getName
- */
+/** @see BinaryXmlPullParser.getName */
 inline val BinaryXmlPullParser.tagName: String
     get() = name
 
-/**
- * Check whether an attribute exists for the current tag.
- */
+/** Check whether an attribute exists for the current tag. */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.hasAttribute(name: String): Boolean = getAttributeIndex(name) != -1
 
-/**
- * @see BinaryXmlPullParser.getAttributeIndex
- */
+/** @see BinaryXmlPullParser.getAttributeIndex */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeIndex(name: String): Int = getAttributeIndex(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeIndexOrThrow
- */
+/** @see BinaryXmlPullParser.getAttributeIndexOrThrow */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeIndexOrThrow(name: String): Int =
     getAttributeIndexOrThrow(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeValue
- */
+/** @see BinaryXmlPullParser.getAttributeValue */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeValue(name: String): String? =
     getAttributeValue(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeValue
- */
+/** @see BinaryXmlPullParser.getAttributeValue */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeValueOrThrow(name: String): String =
     getAttributeValue(getAttributeIndexOrThrow(name))
 
-/**
- * @see BinaryXmlPullParser.getAttributeBytesHex
- */
+/** @see BinaryXmlPullParser.getAttributeBytesHex */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeBytesHex(name: String): ByteArray? =
     getAttributeBytesHex(null, name, null)
 
-/**
- * @see BinaryXmlPullParser.getAttributeBytesHex
- */
+/** @see BinaryXmlPullParser.getAttributeBytesHex */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeBytesHexOrThrow(name: String): ByteArray =
     getAttributeBytesHex(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeBytesBase64
- */
+/** @see BinaryXmlPullParser.getAttributeBytesBase64 */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeBytesBase64(name: String): ByteArray? =
     getAttributeBytesBase64(null, name, null)
 
-/**
- * @see BinaryXmlPullParser.getAttributeBytesBase64
- */
+/** @see BinaryXmlPullParser.getAttributeBytesBase64 */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeBytesBase64OrThrow(name: String): ByteArray =
     getAttributeBytesBase64(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeInt
- */
+/** @see BinaryXmlPullParser.getAttributeInt */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeIntOrDefault(name: String, defaultValue: Int): Int =
     getAttributeInt(null, name, defaultValue)
 
-/**
- * @see BinaryXmlPullParser.getAttributeInt
- */
+/** @see BinaryXmlPullParser.getAttributeInt */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeIntOrThrow(name: String): Int =
     getAttributeInt(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeIntHex
- */
+/** @see BinaryXmlPullParser.getAttributeIntHex */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeIntHexOrDefault(name: String, defaultValue: Int): Int =
     getAttributeIntHex(null, name, defaultValue)
 
-/**
- * @see BinaryXmlPullParser.getAttributeIntHex
- */
+/** @see BinaryXmlPullParser.getAttributeIntHex */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeIntHexOrThrow(name: String): Int =
     getAttributeIntHex(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeLong
- */
+/** @see BinaryXmlPullParser.getAttributeLong */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeLongOrDefault(name: String, defaultValue: Long): Long =
     getAttributeLong(null, name, defaultValue)
 
-/**
- * @see BinaryXmlPullParser.getAttributeLong
- */
+/** @see BinaryXmlPullParser.getAttributeLong */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeLongOrThrow(name: String): Long =
     getAttributeLong(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeLongHex
- */
+/** @see BinaryXmlPullParser.getAttributeLongHex */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeLongHexOrDefault(
     name: String,
     defaultValue: Long
 ): Long = getAttributeLongHex(null, name, defaultValue)
 
-/**
- * @see BinaryXmlPullParser.getAttributeLongHex
- */
+/** @see BinaryXmlPullParser.getAttributeLongHex */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeLongHexOrThrow(name: String): Long =
     getAttributeLongHex(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeFloat
- */
+/** @see BinaryXmlPullParser.getAttributeFloat */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeFloatOrDefault(
     name: String,
     defaultValue: Float
 ): Float = getAttributeFloat(null, name, defaultValue)
 
-/**
- * @see BinaryXmlPullParser.getAttributeFloat
- */
+/** @see BinaryXmlPullParser.getAttributeFloat */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeFloatOrThrow(name: String): Float =
     getAttributeFloat(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeDouble
- */
+/** @see BinaryXmlPullParser.getAttributeDouble */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeDoubleOrDefault(
     name: String,
     defaultValue: Double
 ): Double = getAttributeDouble(null, name, defaultValue)
 
-/**
- * @see BinaryXmlPullParser.getAttributeDouble
- */
+/** @see BinaryXmlPullParser.getAttributeDouble */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeDoubleOrThrow(name: String): Double =
     getAttributeDouble(null, name)
 
-/**
- * @see BinaryXmlPullParser.getAttributeBoolean
- */
+/** @see BinaryXmlPullParser.getAttributeBoolean */
 @Suppress("NOTHING_TO_INLINE")
 inline fun BinaryXmlPullParser.getAttributeBooleanOrDefault(
     name: String,
     defaultValue: Boolean
 ): Boolean = getAttributeBoolean(null, name, defaultValue)
 
-/**
- * @see BinaryXmlPullParser.getAttributeBoolean
- */
+/** @see BinaryXmlPullParser.getAttributeBoolean */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(XmlPullParserException::class)
 inline fun BinaryXmlPullParser.getAttributeBooleanOrThrow(name: String): Boolean =
diff --git a/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt b/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt
index c8cd586..6500a7d 100644
--- a/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt
@@ -20,9 +20,7 @@
 import java.io.IOException
 import java.io.OutputStream
 
-/**
- * Serialize content into [OutputStream] with [BinaryXmlSerializer].
- */
+/** Serialize content into [OutputStream] with [BinaryXmlSerializer]. */
 @Throws(IOException::class)
 inline fun OutputStream.serializeBinaryXml(block: BinaryXmlSerializer.() -> Unit) {
     BinaryXmlSerializer().apply {
@@ -57,54 +55,42 @@
     endTag(null, name)
 }
 
-/**
- * @see BinaryXmlSerializer.attribute
- */
+/** @see BinaryXmlSerializer.attribute */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attribute(name: String, value: String) {
     attribute(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeInterned
- */
+/** @see BinaryXmlSerializer.attributeInterned */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeInterned(name: String, value: String) {
     attributeInterned(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeBytesHex
- */
+/** @see BinaryXmlSerializer.attributeBytesHex */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeBytesHex(name: String, value: ByteArray) {
     attributeBytesHex(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeBytesBase64
- */
+/** @see BinaryXmlSerializer.attributeBytesBase64 */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeBytesBase64(name: String, value: ByteArray) {
     attributeBytesBase64(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeInt
- */
+/** @see BinaryXmlSerializer.attributeInt */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeInt(name: String, value: Int) {
     attributeInt(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeInt
- */
+/** @see BinaryXmlSerializer.attributeInt */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeIntWithDefault(
@@ -117,18 +103,14 @@
     }
 }
 
-/**
- * @see BinaryXmlSerializer.attributeIntHex
- */
+/** @see BinaryXmlSerializer.attributeIntHex */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeIntHex(name: String, value: Int) {
     attributeIntHex(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeIntHex
- */
+/** @see BinaryXmlSerializer.attributeIntHex */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeIntHexWithDefault(
@@ -141,18 +123,14 @@
     }
 }
 
-/**
- * @see BinaryXmlSerializer.attributeLong
- */
+/** @see BinaryXmlSerializer.attributeLong */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeLong(name: String, value: Long) {
     attributeLong(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeLong
- */
+/** @see BinaryXmlSerializer.attributeLong */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeLongWithDefault(
@@ -165,18 +143,14 @@
     }
 }
 
-/**
- * @see BinaryXmlSerializer.attributeLongHex
- */
+/** @see BinaryXmlSerializer.attributeLongHex */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeLongHex(name: String, value: Long) {
     attributeLongHex(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeLongHex
- */
+/** @see BinaryXmlSerializer.attributeLongHex */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeLongHexWithDefault(
@@ -189,18 +163,14 @@
     }
 }
 
-/**
- * @see BinaryXmlSerializer.attributeFloat
- */
+/** @see BinaryXmlSerializer.attributeFloat */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeFloat(name: String, value: Float) {
     attributeFloat(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeFloat
- */
+/** @see BinaryXmlSerializer.attributeFloat */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeFloatWithDefault(
@@ -213,18 +183,14 @@
     }
 }
 
-/**
- * @see BinaryXmlSerializer.attributeDouble
- */
+/** @see BinaryXmlSerializer.attributeDouble */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeDouble(name: String, value: Double) {
     attributeDouble(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeDouble
- */
+/** @see BinaryXmlSerializer.attributeDouble */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeDoubleWithDefault(
@@ -237,18 +203,14 @@
     }
 }
 
-/**
- * @see BinaryXmlSerializer.attributeBoolean
- */
+/** @see BinaryXmlSerializer.attributeBoolean */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeBoolean(name: String, value: Boolean) {
     attributeBoolean(null, name, value)
 }
 
-/**
- * @see BinaryXmlSerializer.attributeBoolean
- */
+/** @see BinaryXmlSerializer.attributeBoolean */
 @Suppress("NOTHING_TO_INLINE")
 @Throws(IOException::class)
 inline fun BinaryXmlSerializer.attributeBooleanWithDefault(
diff --git a/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt b/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt
index a61489c..3ef284b 100644
--- a/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt
+++ b/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt
@@ -22,13 +22,13 @@
 
 object PackageVersionMigration {
     /**
-     * Maps existing permission and app-op version to a unified version during OTA upgrade. The
-     * new unified version is used in determining the upgrade steps for a package (for both
-     * permission and app-ops).
+     * Maps existing permission and app-op version to a unified version during OTA upgrade. The new
+     * unified version is used in determining the upgrade steps for a package (for both permission
+     * and app-ops).
      *
      * @return unified permission/app-op version
      * @throws IllegalStateException if the method is called when there is nothing to migrate i.e.
-     * permission and app-op file does not exist.
+     *   permission and app-op file does not exist.
      */
     internal fun getVersion(userId: Int): Int {
         val permissionMigrationHelper =
diff --git a/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt b/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt
index e6b4e3e..3d835e8 100644
--- a/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt
+++ b/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt
@@ -23,15 +23,11 @@
 object PermissionApex {
     private const val MODULE_NAME = "com.android.permission"
 
-    /**
-     * @see ApexEnvironment.getDeviceProtectedDataDir
-     */
+    /** @see ApexEnvironment.getDeviceProtectedDataDir */
     val systemDataDirectory: File
         get() = apexEnvironment.deviceProtectedDataDir
 
-    /**
-     * @see ApexEnvironment.getDeviceProtectedDataDirForUser
-     */
+    /** @see ApexEnvironment.getDeviceProtectedDataDirForUser */
     fun getUserDataDirectory(userId: Int): File =
         apexEnvironment.getDeviceProtectedDataDirForUser(UserHandle.of(userId))
 
diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp
index 0275c7d..6e4069f 100644
--- a/services/tests/displayservicetests/Android.bp
+++ b/services/tests/displayservicetests/Android.bp
@@ -40,6 +40,7 @@
         "services.core",
         "servicestests-utils",
         "testables",
+        "TestParameterInjector",
     ],
 
     defaults: [
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 2396905..d021f1d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -114,17 +114,18 @@
 import com.android.server.display.DisplayManagerService.DeviceStateListener;
 import com.android.server.display.DisplayManagerService.SyncRoot;
 import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.display.notifications.DisplayNotificationManager;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.lights.LightsManager;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.sensors.SensorManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
+import com.google.common.truth.Expect;
+
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
 
-import com.google.common.truth.Expect;
-
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -201,9 +202,12 @@
                 @Override
                 LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
                         Handler handler, DisplayAdapter.Listener displayAdapterListener,
-                        DisplayManagerFlags flags) {
+                        DisplayManagerFlags flags,
+                        DisplayNotificationManager displayNotificationManager) {
                     return new LocalDisplayAdapter(syncRoot, context, handler,
-                            displayAdapterListener, flags, new LocalDisplayAdapter.Injector() {
+                            displayAdapterListener, flags,
+                            mMockedDisplayNotificationManager,
+                            new LocalDisplayAdapter.Injector() {
                         @Override
                         public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
                             return mSurfaceControlProxy;
@@ -248,13 +252,15 @@
         @Override
         LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
                 Handler handler, DisplayAdapter.Listener displayAdapterListener,
-                DisplayManagerFlags flags) {
+                DisplayManagerFlags flags,
+                DisplayNotificationManager displayNotificationManager) {
             return new LocalDisplayAdapter(
                     syncRoot,
                     context,
                     handler,
                     displayAdapterListener,
                     flags,
+                    mMockedDisplayNotificationManager,
                     new LocalDisplayAdapter.Injector() {
                         @Override
                         public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
@@ -288,6 +294,7 @@
 
     private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
 
+    @Mock DisplayNotificationManager mMockedDisplayNotificationManager;
     @Mock IMediaProjectionManager mMockProjectionService;
     @Mock IVirtualDeviceManager mIVirtualDeviceManager;
     @Mock InputManagerInternal mMockInputManagerInternal;
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 147e8f2..9ac0062 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -60,6 +60,7 @@
 import com.android.server.display.LocalDisplayAdapter.BacklightAdapter;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.mode.DisplayModeDirector;
+import com.android.server.display.notifications.DisplayNotificationManager;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
 
@@ -113,6 +114,8 @@
     @Mock
     private LogicalLight mMockedBacklight;
     @Mock
+    private DisplayNotificationManager mMockedDisplayNotificationManager;
+    @Mock
     private DisplayManagerFlags mFlags;
 
     private Handler mHandler;
@@ -148,7 +151,7 @@
         mInjector = new Injector();
         when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true);
         mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler,
-                mListener, mFlags, mInjector);
+                mListener, mFlags, mMockedDisplayNotificationManager, mInjector);
         spyOn(mAdapter);
         doReturn(mMockedContext).when(mAdapter).getOverlayContext();
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
index c7c09b5..ec27f9d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
@@ -44,12 +44,12 @@
 import android.test.mock.MockContentResolver;
 import android.view.Display;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.R;
 import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.LocalServices;
+import com.android.internal.util.test.LocalServiceKeeperRule;
 import com.android.server.SystemService;
 import com.android.server.twilight.TwilightListener;
 import com.android.server.twilight.TwilightManager;
@@ -57,6 +57,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
@@ -90,9 +91,13 @@
         ColorDisplayManager.COLOR_MODE_BOOSTED,
     };
 
+    @Rule
+    public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule();
+
     @Before
     public void setUp() {
-        mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+        mContext = Mockito.spy(new ContextWrapper(
+                InstrumentationRegistry.getInstrumentation().getTargetContext()));
         doReturn(mContext).when(mContext).getApplicationContext();
 
         final Resources res = Mockito.spy(mContext.getResources());
@@ -112,43 +117,36 @@
         doReturn(am).when(mContext).getSystemService(Context.ALARM_SERVICE);
 
         mTwilightManager = new MockTwilightManager();
-        LocalServices.addService(TwilightManager.class, mTwilightManager);
+        mLocalServiceKeeperRule.overrideLocalService(TwilightManager.class, mTwilightManager);
 
         mDisplayTransformManager = Mockito.mock(DisplayTransformManager.class);
         doReturn(true).when(mDisplayTransformManager).needsLinearColorMatrix();
-        LocalServices.addService(DisplayTransformManager.class, mDisplayTransformManager);
+        mLocalServiceKeeperRule.overrideLocalService(
+                DisplayTransformManager.class, mDisplayTransformManager);
 
         mDisplayManagerInternal = Mockito.mock(DisplayManagerInternal.class);
-        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
-        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal);
+        mLocalServiceKeeperRule.overrideLocalService(
+                DisplayManagerInternal.class, mDisplayManagerInternal);
 
         mCds = new ColorDisplayService(mContext);
         mBinderService = mCds.new BinderService();
-        LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
+        mLocalServiceKeeperRule.overrideLocalService(
+                ColorDisplayService.ColorDisplayServiceInternal.class,
                 mCds.new ColorDisplayServiceInternal());
     }
 
     @After
     public void tearDown() {
-        /*
-         * Wait for internal {@link Handler} to finish processing pending messages, so that test
-         * code can safelyremove {@link DisplayTransformManager} mock from {@link LocalServices}.
-         */
-        mCds.mHandler.runWithScissors(() -> { /* nop */ }, /* timeout */ 1000);
+        // synchronously cancel all animations
+        mCds.mHandler.runWithScissors(() -> mCds.cancelAllAnimators(), /* timeout */ 1000);
         mCds = null;
 
-        LocalServices.removeServiceForTest(TwilightManager.class);
         mTwilightManager = null;
 
-        LocalServices.removeServiceForTest(DisplayTransformManager.class);
-
         mUserId = UserHandle.USER_NULL;
         mContext = null;
 
         FakeSettingsProvider.clearSettingsProvider();
-
-        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
-        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
     }
 
     @Test
@@ -1249,10 +1247,10 @@
     private void startService() {
         Secure.putIntForUser(mContext.getContentResolver(), Secure.USER_SETUP_COMPLETE, 1, mUserId);
 
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            mCds.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-            mCds.onUserChanged(mUserId);
-        });
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                () -> mCds.onBootPhase(SystemService.PHASE_BOOT_COMPLETED));
+        // onUserChanged cancels running animations, and should be called in handler thread
+        mCds.mHandler.runWithScissors(() -> mCds.onUserChanged(mUserId), 1000);
     }
 
     /**
diff --git a/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java
new file mode 100644
index 0000000..d7c35ed
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.notifications;
+
+import static android.app.Notification.FLAG_ONGOING_EVENT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.display.notifications.DisplayNotificationManager.Injector;
+
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link DisplayNotificationManager}
+ * Run: atest DisplayNotificationManagerTest
+ */
+@SmallTest
+@RunWith(TestParameterInjector.class)
+public class DisplayNotificationManagerTest {
+    @Mock
+    private Injector mMockedInjector;
+    @Mock
+    private NotificationManager mMockedNotificationManager;
+    @Mock
+    private DisplayManagerFlags mMockedFlags;
+    @Captor
+    private ArgumentCaptor<String> mNotifyTagCaptor;
+    @Captor
+    private ArgumentCaptor<Integer> mNotifyNoteIdCaptor;
+    @Captor
+    private ArgumentCaptor<Notification> mNotifyAsUserNotificationCaptor;
+
+    /** Setup tests. */
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testNotificationOnHotplugConnectionError() {
+        var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true,
+                /*isErrorHandlingEnabled=*/ true);
+        dnm.onHotplugConnectionError();
+        assertExpectedNotification();
+    }
+
+    @Test
+    public void testNoErrorNotification(
+            @TestParameter final boolean isNotificationManagerAvailable,
+            @TestParameter final boolean isErrorHandlingEnabled) {
+        /* This case is tested by #testNotificationOnHotplugConnectionError */
+        assumeFalse(isNotificationManagerAvailable && isErrorHandlingEnabled);
+        var dnm = createDisplayNotificationManager(isNotificationManagerAvailable,
+                isErrorHandlingEnabled);
+        dnm.onHotplugConnectionError();
+        verify(mMockedNotificationManager, never()).notify(anyString(), anyInt(), any());
+    }
+
+    private DisplayNotificationManager createDisplayNotificationManager(
+            final boolean isNotificationManagerAvailable,
+            final boolean isErrorHandlingEnabled) {
+        when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled()).thenReturn(
+                isErrorHandlingEnabled);
+        when(mMockedInjector.getNotificationManager()).thenReturn(
+                (isNotificationManagerAvailable) ? mMockedNotificationManager : null);
+        final var displayNotificationManager = new DisplayNotificationManager(mMockedFlags,
+                ApplicationProvider.getApplicationContext(), mMockedInjector);
+        displayNotificationManager.onBootCompleted();
+        return displayNotificationManager;
+    }
+
+    private void assertExpectedNotification() {
+        verify(mMockedNotificationManager).notify(
+                mNotifyTagCaptor.capture(),
+                mNotifyNoteIdCaptor.capture(),
+                mNotifyAsUserNotificationCaptor.capture());
+        assertThat(mNotifyTagCaptor.getValue()).isEqualTo("DisplayNotificationManager");
+        assertThat((int) mNotifyNoteIdCaptor.getValue()).isEqualTo(1);
+        final var notification = mNotifyAsUserNotificationCaptor.getValue();
+        assertThat(notification.getChannelId()).isEqualTo("ALERTS");
+        assertThat(notification.category).isEqualTo(Notification.CATEGORY_ERROR);
+        assertThat(notification.visibility).isEqualTo(Notification.VISIBILITY_PUBLIC);
+        assertThat(notification.flags & FLAG_ONGOING_EVENT).isEqualTo(0);
+        assertThat(notification.when).isEqualTo(0);
+        assertThat(notification.getTimeoutAfter()).isEqualTo(30000L);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 5b51963..21e3b34 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -62,8 +62,6 @@
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE;
-import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED;
-import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED;
@@ -75,7 +73,6 @@
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WINDOW;
-import static com.android.server.alarm.AlarmManagerService.Constants.KEY_EXACT_ALARM_DENY_LIST;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_DEVICE_IDLE_FUZZ;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INTERVAL;
@@ -85,7 +82,6 @@
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_WINDOW;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_PRIORITY_ALARM_DELAY;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_TEMPORARY_QUOTA_BUMP;
-import static com.android.server.alarm.AlarmManagerService.Constants.MAX_EXACT_ALARM_DENY_LIST_SIZE;
 import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX;
 import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY;
 import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK;
@@ -799,47 +795,6 @@
     }
 
     @Test
-    public void updatingExactAlarmDenyList() {
-        ArraySet<String> denyListed = new ArraySet<>(new String[]{
-                "com.example.package1",
-                "com.example.package2",
-                "com.example.package3",
-        });
-        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST,
-                "com.example.package1,com.example.package2,com.example.package3");
-        assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST);
-
-
-        denyListed = new ArraySet<>(new String[]{
-                "com.example.package1",
-                "com.example.package4",
-        });
-        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST,
-                "com.example.package1,com.example.package4");
-        assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST);
-
-        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "");
-        assertEquals(0, mService.mConstants.EXACT_ALARM_DENY_LIST.size());
-    }
-
-    @Test
-    public void exactAlarmDenyListMaxSize() {
-        final ArraySet<String> expectedSet = new ArraySet<>();
-        final StringBuilder sb = new StringBuilder("package1");
-        expectedSet.add("package1");
-        for (int i = 2; i <= 2 * MAX_EXACT_ALARM_DENY_LIST_SIZE; i++) {
-            sb.append(",package");
-            sb.append(i);
-            if (i <= MAX_EXACT_ALARM_DENY_LIST_SIZE) {
-                expectedSet.add("package" + i);
-            }
-        }
-        assertEquals(MAX_EXACT_ALARM_DENY_LIST_SIZE, expectedSet.size());
-        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, sb.toString());
-        assertEquals(expectedSet, mService.mConstants.EXACT_ALARM_DENY_LIST);
-    }
-
-    @Test
     public void positiveWhileIdleQuotas() {
         setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, -3);
         assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA);
@@ -2212,50 +2167,30 @@
     }
 
     @Test
-    public void hasScheduleExactAlarmBinderCallNotDenyListedPreT() throws RemoteException {
+    public void hasScheduleExactAlarmBinderCallPreT() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT);
+        mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT);
         assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_IGNORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_IGNORED);
         assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
     }
 
     @Test
-    public void hasScheduleExactAlarmBinderCallDenyListedPreT() throws RemoteException {
-        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
-        mockScheduleExactAlarmStatePreT(true, true, MODE_ERRORED);
-        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
-        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
-        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
-        mockScheduleExactAlarmStatePreT(true, true, MODE_IGNORED);
-        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
-        mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED);
-        assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-    }
-
-    @Test
     public void hasScheduleExactAlarmBinderCallNotDeclaredPreT() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmStatePreT(false, false, MODE_DEFAULT);
+        mockScheduleExactAlarmStatePreT(false, MODE_DEFAULT);
         assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
 
-        mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED);
-        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
-        mockScheduleExactAlarmStatePreT(false, true, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(false, MODE_ALLOWED);
         assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
     }
 
@@ -2281,41 +2216,33 @@
         mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
 
         // No permission, no exemption.
-        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
-        assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
-
-        // No permission, no exemption.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         // Policy permission only, no exemption.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         mockUseExactAlarmState(true);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         mockUseExactAlarmState(false);
 
         // User permission only, no exemption.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT);
-        assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
-
-        // User permission only, no exemption.
-        mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         // No permission, exemption.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(true);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         // No permission, exemption.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(false);
         doReturn(true).when(() -> UserHandle.isCore(TEST_CALLING_UID));
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         // Both permissions and exemption.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         mockUseExactAlarmState(true);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
     }
@@ -2514,17 +2441,12 @@
         assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
     }
 
-    private void mockScheduleExactAlarmStatePreT(boolean declared, boolean denyList, int mode) {
+    private void mockScheduleExactAlarmStatePreT(boolean declared, int mode) {
         String[] requesters = declared ? new String[]{TEST_CALLING_PACKAGE} : EmptyArray.STRING;
         when(mPermissionManagerInternal.getAppOpPermissionPackages(SCHEDULE_EXACT_ALARM))
                 .thenReturn(requesters);
         mService.refreshExactAlarmCandidates();
 
-        if (denyList) {
-            setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, TEST_CALLING_PACKAGE);
-        } else {
-            setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "");
-        }
         when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID,
                 TEST_CALLING_PACKAGE)).thenReturn(mode);
     }
@@ -2556,7 +2478,7 @@
     public void alarmClockBinderCallWithoutPermission() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
 
         final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2630,7 +2552,7 @@
     public void exactBinderCallWithAllowlist() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
         // If permission is denied, only then allowlist will be checked.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
 
         final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2650,7 +2572,7 @@
     public void exactAllowWhileIdleBinderCallWithSEAPermission() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         final PendingIntent alarmPi = getNewMockPendingIntent();
         mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
                 FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
@@ -2676,7 +2598,7 @@
         mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
 
         mockUseExactAlarmState(true);
-        mockScheduleExactAlarmStatePreT(false, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(false, MODE_ERRORED);
         final PendingIntent alarmPi = getNewMockPendingIntent();
         mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
                 FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
@@ -2700,7 +2622,7 @@
     public void exactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
         // If permission is denied, only then allowlist will be checked.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
 
         final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2726,7 +2648,7 @@
     public void exactBinderCallsWithoutPermissionWithoutAllowlist() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false);
 
         final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2836,7 +2758,7 @@
     public void binderCallWithUserAllowlist() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
         when(mAppStateTracker.isUidPowerSaveUserExempt(TEST_CALLING_UID)).thenReturn(true);
 
@@ -3052,135 +2974,11 @@
     }
 
     @Test
-    public void denyListChanged() {
-        mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"});
-        when(mActivityManagerInternal.getStartedUserIds()).thenReturn(EmptyArray.INT);
-
-        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2,p4,p5");
-
-        final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-        verify(mService.mHandler, times(2)).sendMessageAtTime(messageCaptor.capture(),
-                anyLong());
-
-        final List<Message> messages = messageCaptor.getAllValues();
-        for (final Message msg : messages) {
-            assertTrue("Unwanted message sent to handler: " + msg.what,
-                    msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_ADDED
-                            || msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED);
-            mService.mHandler.handleMessage(msg);
-        }
-
-        ArraySet<String> added = new ArraySet<>(new String[]{"p4", "p5"});
-        verify(mService).handleChangesToExactAlarmDenyList(eq(added), eq(true));
-
-        ArraySet<String> removed = new ArraySet<>(new String[]{"p1", "p3"});
-        verify(mService).handleChangesToExactAlarmDenyList(eq(removed), eq(false));
-    }
-
-    @Test
-    public void permissionGrantedDueToDenyList() {
-        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
-        final String[] packages = {"example.package.1", "example.package.2"};
-
-        final int appId1 = 232;
-        final int appId2 = 431;
-
-        final int userId1 = 42;
-        final int userId2 = 53;
-
-        registerAppIds(packages, new Integer[]{appId1, appId2});
-
-        when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2});
-
-        when(mPermissionManagerInternal.getAppOpPermissionPackages(
-                SCHEDULE_EXACT_ALARM)).thenReturn(packages);
-        mService.refreshExactAlarmCandidates();
-
-        final long allowListDuration = 53442;
-        when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(
-                allowListDuration);
-
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED);
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT);
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED);
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED);
-
-        mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), false);
-
-        // No permission revoked.
-        verify(mService, never()).removeExactAlarmsOnPermissionRevoked(anyInt(), anyString(),
-                anyBoolean());
-
-        // Permission got granted only for (appId1, userId2).
-        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
-        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
-        final ArgumentCaptor<UserHandle> userCaptor = ArgumentCaptor.forClass(UserHandle.class);
-
-        verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), userCaptor.capture(),
-                isNull(), bundleCaptor.capture());
-
-        assertEquals(userId2, userCaptor.getValue().getIdentifier());
-
-        // Validate the intent.
-        assertEquals(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
-                intentCaptor.getValue().getAction());
-        assertEquals(packages[0], intentCaptor.getValue().getPackage());
-
-        // Validate the options.
-        final BroadcastOptions bOptions = new BroadcastOptions(bundleCaptor.getValue());
-        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
-                bOptions.getTemporaryAppAllowlistType());
-        assertEquals(allowListDuration, bOptions.getTemporaryAppAllowlistDuration());
-        assertEquals(PowerExemptionManager.REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
-                bOptions.getTemporaryAppAllowlistReasonCode());
-    }
-
-    @Test
-    public void permissionRevokedDueToDenyList() {
-        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
-        final String[] packages = {"example.package.1", "example.package.2"};
-
-        final int appId1 = 232;
-        final int appId2 = 431;
-
-        final int userId1 = 42;
-        final int userId2 = 53;
-
-        registerAppIds(packages, new Integer[]{appId1, appId2});
-
-        when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2});
-
-        when(mPermissionManagerInternal.getAppOpPermissionPackages(
-                SCHEDULE_EXACT_ALARM)).thenReturn(packages);
-        mService.refreshExactAlarmCandidates();
-
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED);
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT);
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED);
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED);
-
-        mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), true);
-
-        // Permission got revoked only for (appId1, userId2)
-        verify(mService, never()).removeExactAlarmsOnPermissionRevoked(
-                eq(UserHandle.getUid(userId1, appId1)), eq(packages[0]), eq(true));
-        verify(mService, never()).removeExactAlarmsOnPermissionRevoked(
-                eq(UserHandle.getUid(userId1, appId2)), eq(packages[1]), eq(true));
-        verify(mService, never()).removeExactAlarmsOnPermissionRevoked(
-                eq(UserHandle.getUid(userId2, appId2)), eq(packages[1]), eq(true));
-
-        verify(mService).removeExactAlarmsOnPermissionRevoked(
-                eq(UserHandle.getUid(userId2, appId1)), eq(packages[0]), eq(true));
-    }
-
-    @Test
     public void opChangedPermissionRevoked() throws Exception {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
         mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
 
         mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
         assertAndHandleMessageSync(REMOVE_EXACT_ALARMS);
@@ -3193,20 +2991,7 @@
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
 
         mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
-
-        mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
-
-        verify(mService.mHandler, never()).sendMessageAtTime(
-                argThat(m -> m.what == REMOVE_EXACT_ALARMS), anyLong());
-    }
-
-    @Test
-    public void opChangedNoPermissionChangeDueToDenyList() throws Exception {
-        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
-
-        mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
-        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
 
         mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
 
@@ -3222,7 +3007,7 @@
         when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs);
 
         mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
 
         final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -3482,7 +3267,7 @@
                 .putExtra(Intent.EXTRA_REPLACING, true);
 
         mockUseExactAlarmState(false);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
         assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
 
@@ -3490,7 +3275,7 @@
         assertEquals(5, mService.mAlarmStore.size());
 
         mockUseExactAlarmState(true);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
         assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
 
@@ -3498,7 +3283,7 @@
         assertEquals(5, mService.mAlarmStore.size());
 
         mockUseExactAlarmState(false);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
         assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
 
@@ -3653,16 +3438,16 @@
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
         mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false);
 
-        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
+        mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT);
+        assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+
+        mockScheduleExactAlarmStatePreT(false, MODE_ALLOWED);
         assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
 
-        mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
-        assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
-
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
     }
 
@@ -3671,11 +3456,11 @@
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
 
         mockScheduleExactAlarmState(true);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
 
         mockScheduleExactAlarmState(false);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
     }
 
diff --git a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
index 2ffe4aa..df46054 100644
--- a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -1079,7 +1079,7 @@
         GetSupportedPowerMonitorsResult result = new GetSupportedPowerMonitorsResult();
         mService.getSupportedPowerMonitorsImpl(result);
         assertThat(result.powerMonitors).isNotNull();
-        assertThat(Arrays.stream(result.powerMonitors).map(pm -> pm.name).toList())
+        assertThat(Arrays.stream(result.powerMonitors).map(PowerMonitor::getName).toList())
                 .containsAtLeast(
                         "energyconsumer0",
                         "BLUETOOTH/1",
@@ -1130,7 +1130,7 @@
         mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult);
         Map<String, PowerMonitor> map =
                 Arrays.stream(supportedPowerMonitorsResult.powerMonitors)
-                        .collect(Collectors.toMap(pm -> pm.name, pm -> pm));
+                        .collect(Collectors.toMap(PowerMonitor::getName, pm -> pm));
         PowerMonitor consumer1 = map.get("energyconsumer0");
         PowerMonitor consumer2 = map.get("BLUETOOTH/1");
         PowerMonitor measurement1 = map.get("[channelname0]:channelsubsystem0");
@@ -1196,6 +1196,6 @@
         supportedPowerMonitorsResult = new GetSupportedPowerMonitorsResult();
         mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult);
         assertThat(Arrays.stream(supportedPowerMonitorsResult.powerMonitors)
-                .map(pm -> pm.name).toList()).contains("energyconsumer0");
+                .map(PowerMonitor::getName).toList()).contains("energyconsumer0");
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 49f22ec..cf315a4 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -33,6 +33,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
@@ -53,12 +54,18 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Icon;
 import android.hardware.display.DisplayManagerGlobal;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.LocaleList;
 import android.os.UserHandle;
+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.provider.Settings;
 import android.testing.TestableContext;
 import android.view.Display;
@@ -93,8 +100,13 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * APCT tests for {@link AccessibilityManagerService}.
@@ -104,6 +116,10 @@
     public final A11yTestableContext mTestableContext = new A11yTestableContext(
             ApplicationProvider.getApplicationContext());
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final int ACTION_ID = 20;
     private static final String LABEL = "label";
     private static final String INTENT_ACTION = "TESTACTION";
@@ -204,6 +220,8 @@
                 mA11yms.getCurrentUserIdLocked());
         when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo);
         mMockResolveInfo.serviceInfo = mock(ServiceInfo.class);
+        mMockResolveInfo.serviceInfo.packageName = "packageName";
+        mMockResolveInfo.serviceInfo.name = "className";
         mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class);
 
         when(mMockBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient);
@@ -581,6 +599,73 @@
                 ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
     }
 
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
+    // Test old behavior to validate lock detection for the old (locked access) case.
+    public void testPackageMonitorScanPackages_scansWhileHoldingLock() {
+        setupAccessibilityServiceConnection(0);
+        final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
+        when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+                .thenReturn(List.of(mMockResolveInfo));
+        when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
+
+        final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED);
+        packageIntent.setData(Uri.parse("test://package"));
+        packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked());
+        packageIntent.putExtra(Intent.EXTRA_REPLACING, true);
+        mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent);
+
+        assertThat(lockState.get()).containsExactly(true);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
+    public void testPackageMonitorScanPackages_scansWithoutHoldingLock() {
+        setupAccessibilityServiceConnection(0);
+        final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
+        when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+                .thenReturn(List.of(mMockResolveInfo));
+        when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
+
+        final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED);
+        packageIntent.setData(Uri.parse("test://package"));
+        packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked());
+        packageIntent.putExtra(Intent.EXTRA_REPLACING, true);
+        mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent);
+
+        assertThat(lockState.get()).containsExactly(false);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
+    public void testSwitchUserScanPackages_scansWithoutHoldingLock() {
+        setupAccessibilityServiceConnection(0);
+        final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
+        when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+                .thenReturn(List.of(mMockResolveInfo));
+        when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
+
+        mA11yms.switchUser(mA11yms.getCurrentUserIdLocked() + 1);
+
+        assertThat(lockState.get()).containsExactly(false);
+    }
+
+    // Single package intents can trigger multiple PackageMonitor callbacks.
+    // Collect the state of the lock in a set, since tests only care if calls
+    // were all locked or all unlocked.
+    private AtomicReference<Set<Boolean>> collectLockStateWhilePackageScanning() {
+        final AtomicReference<Set<Boolean>> lockState =
+                new AtomicReference<>(new HashSet<Boolean>());
+        doAnswer((Answer<XmlResourceParser>) invocation -> {
+            lockState.updateAndGet(set -> {
+                set.add(mA11yms.unsafeIsLockHeld());
+                return set;
+            });
+            return null;
+        }).when(mMockResolveInfo.serviceInfo).loadXmlMetaData(any(), any());
+        return lockState;
+    }
+
     private void mockManageAccessibilityGranted(TestableContext context) {
         context.getTestablePermissions().setPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
                 PackageManager.PERMISSION_GRANTED);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 1cd61e9..efcdbd4 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -44,6 +44,10 @@
 import android.graphics.PointF;
 import android.os.Looper;
 import android.os.SystemClock;
+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.testing.DexmakerShareClassLoaderRule;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -56,6 +60,7 @@
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.accessibility.EventStreamTransformation;
+import com.android.server.accessibility.Flags;
 import com.android.server.accessibility.utils.GestureLogParser;
 import com.android.server.testutils.OffsettableClock;
 
@@ -76,6 +81,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
+
 @RunWith(AndroidJUnit4.class)
 public class TouchExplorerTest {
 
@@ -119,6 +125,9 @@
     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
             new DexmakerShareClassLoaderRule();
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     /**
      * {@link TouchExplorer#sendDownForAllNotInjectedPointers} injecting events with the same object
      * is resulting {@link ArgumentCaptor} to capture events with last state. Before implementation
@@ -161,11 +170,16 @@
         goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
         // Wait for transiting to touch exploring state.
         mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
-        moveEachPointers(mLastEvent, p(10, 10));
-        send(mLastEvent);
+        assertState(STATE_TOUCH_EXPLORING);
+        // Manually construct the next move event. Using moveEachPointers() will batch the move
+        // event which produces zero movement for some reason.
+        float[] x = new float[1];
+        float[] y = new float[1];
+        x[0] = mLastEvent.getX(0) + mTouchSlop;
+        y[0] = mLastEvent.getY(0) + mTouchSlop;
+        send(manyPointerEvent(ACTION_MOVE, x, y));
         goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER);
         assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
-        assertState(STATE_TOUCH_EXPLORING);
     }
 
     /**
@@ -173,7 +187,8 @@
      * change the coordinates.
      */
     @Test
-    public void testOneFingerMoveWithExtraMoveEvents() {
+    @RequiresFlagsEnabled(Flags.FLAG_REDUCE_TOUCH_EXPLORATION_SENSITIVITY)
+    public void testOneFingerMoveWithExtraMoveEvents_generatesOneMoveEvent() {
         goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
         // Inject a set of move events that have the same coordinates as the down event.
         moveEachPointers(mLastEvent, p(0, 0));
@@ -181,7 +196,33 @@
         // Wait for transition to touch exploring state.
         mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
         // Now move for real.
-        moveEachPointers(mLastEvent, p(10, 10));
+        moveAtLeastTouchSlop(mLastEvent);
+        send(mLastEvent);
+        // One more move event with no change.
+        moveEachPointers(mLastEvent, p(0, 0));
+        send(mLastEvent);
+        goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER);
+        assertCapturedEvents(
+                ACTION_HOVER_ENTER,
+                ACTION_HOVER_MOVE,
+                ACTION_HOVER_EXIT);
+    }
+
+    /**
+     * Test the case where ACTION_DOWN is followed by a number of ACTION_MOVE events that do not
+     * change the coordinates.
+     */
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REDUCE_TOUCH_EXPLORATION_SENSITIVITY)
+    public void testOneFingerMoveWithExtraMoveEvents_generatesThreeMoveEvent() {
+        goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
+        // Inject a set of move events that have the same coordinates as the down event.
+        moveEachPointers(mLastEvent, p(0, 0));
+        send(mLastEvent);
+        // Wait for transition to touch exploring state.
+        mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
+        // Now move for real.
+        moveAtLeastTouchSlop(mLastEvent);
         send(mLastEvent);
         // One more move event with no change.
         moveEachPointers(mLastEvent, p(0, 0));
@@ -242,7 +283,7 @@
         moveEachPointers(mLastEvent, p(0, 0), p(0, 0));
         send(mLastEvent);
         // Now move for real.
-        moveEachPointers(mLastEvent, p(10, 10), p(10, 10));
+        moveEachPointers(mLastEvent, p(mTouchSlop, mTouchSlop), p(mTouchSlop, mTouchSlop));
         send(mLastEvent);
         goToStateClearFrom(STATE_DRAGGING_2FINGERS);
         assertCapturedEvents(ACTION_DOWN, ACTION_MOVE, ACTION_MOVE, ACTION_MOVE, ACTION_UP);
@@ -251,7 +292,7 @@
     @Test
     public void testUpEvent_OneFingerMove_clearStateAndInjectHoverEvents() {
         goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
-        moveEachPointers(mLastEvent, p(10, 10));
+        moveAtLeastTouchSlop(mLastEvent);
         send(mLastEvent);
         // Wait 10 ms to make sure that hover enter and exit are not scheduled for the same moment.
         mHandler.fastForward(10);
@@ -277,7 +318,7 @@
 
         // Wait for the finger moving to the second view.
         mHandler.fastForward(oneThirdUserIntentTimeout);
-        moveEachPointers(mLastEvent, p(10, 10));
+        moveAtLeastTouchSlop(mLastEvent);
         send(mLastEvent);
 
         // Wait for the finger lifting from the second view.
@@ -402,7 +443,6 @@
         // Manually construct the next move event. Using moveEachPointers() will batch the move
         // event onto the pointer up event which will mean that the move event still has a pointer
         // count of 3.
-        // Todo: refactor to avoid using batching as there is no special reason to do it that way.
         float[] x = new float[2];
         float[] y = new float[2];
         x[0] = mLastEvent.getX(0) + 100;
@@ -734,6 +774,9 @@
         }
     }
 
+    private void moveAtLeastTouchSlop(MotionEvent event) {
+        moveEachPointers(event, p(2 * mTouchSlop, 0));
+    }
     /**
      * A {@link android.os.Handler} that doesn't process messages until {@link #fastForward(int)} is
      * invoked.
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
index 70527ce..44d6760 100644
--- a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
@@ -238,7 +238,7 @@
         }
 
         @Override
-        Handler getHandler(Handler.Callback callback) {
+        Handler newHandler(Handler.Callback callback) {
             if (mTestHandler == null) {
                 mTestHandler = new TestHandler(mHandler.getLooper(), callback, mImmediate);
             }
@@ -250,14 +250,18 @@
             return mTestHandler;
         }
 
+        /**
+         * This override returns the tracker supplied in the constructor.  It does not create a
+         * new one.
+         */
         @Override
-        AnrTimer.CpuTracker getTracker() {
+        AnrTimer.CpuTracker newTracker() {
             return mTracker;
         }
 
         /** For test purposes, always enable the feature. */
         @Override
-        boolean getFeatureEnabled() {
+        boolean isFeatureEnabled() {
             return true;
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 0230d77..e3e708e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -47,6 +47,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
@@ -64,6 +65,7 @@
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.IBiometricPromptStatusListener;
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceReceiver;
@@ -1751,6 +1753,45 @@
         verifyNoMoreInteractions(callback);
     }
 
+    @Test
+    public void testRegisterBiometricPromptOnKeyguardCallback_authenticationAlreadyStarted()
+            throws Exception {
+        final IBiometricPromptStatusListener callback =
+                mock(IBiometricPromptStatusListener.class);
+
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+                true /* requireConfirmation */, null /* authenticators */);
+        mBiometricService.mImpl.registerBiometricPromptStatusListener(callback);
+
+        verify(callback).onBiometricPromptShowing();
+    }
+
+    @Test
+    public void testRegisterBiometricPromptOnKeyguardCallback_startAuth_dismissDialog()
+            throws Exception {
+        final IBiometricPromptStatusListener listener =
+                mock(IBiometricPromptStatusListener.class);
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        mBiometricService.mImpl.registerBiometricPromptStatusListener(listener);
+        waitForIdle();
+
+        verify(listener).onBiometricPromptIdle();
+
+        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+                true /* requireConfirmation */, null /* authenticators */);
+        waitForIdle();
+
+        verify(listener).onBiometricPromptShowing();
+
+        final byte[] hat = generateRandomHAT();
+        mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
+                BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED, hat);
+        waitForIdle();
+
+        verify(listener, times(2)).onBiometricPromptIdle();
+    }
+
     // Helper methods
 
     private int invokeCanAuthenticate(BiometricService service, int authenticators)
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 9e5a047..3a3dd6e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -36,6 +36,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.content.ComponentName;
+import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.common.AuthenticateReason;
 import android.hardware.biometrics.common.ICancellationSignal;
@@ -59,6 +60,7 @@
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.face.UsageStats;
 
 import org.junit.Before;
@@ -103,7 +105,7 @@
     @Mock
     private ClientMonitorCallback mCallback;
     @Mock
-    private Sensor.HalSessionCallback mHalSessionCallback;
+    private AidlResponseHandler mAidlResponseHandler;
     @Mock
     private ActivityTaskManager mActivityTaskManager;
     @Mock
@@ -112,6 +114,8 @@
     private AuthSessionCoordinator mAuthSessionCoordinator;
     @Mock
     private BiometricManager mBiometricManager;
+    @Mock
+    private LockoutTracker mLockoutTracker;
     @Captor
     private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
     @Captor
@@ -236,27 +240,63 @@
         verify(mCallback).onClientFinished(client, true);
     }
 
+    @Test
+    public void authWithNoLockout() throws RemoteException {
+        when(mLockoutTracker.getLockoutModeForUser(anyInt())).thenReturn(
+                LockoutTracker.LOCKOUT_NONE);
+
+        final FaceAuthenticationClient client = createClientWithLockoutTracker(mLockoutTracker);
+        client.start(mCallback);
+
+        verify(mHal).authenticate(OP_ID);
+    }
+
+    @Test
+    public void authWithLockout() throws RemoteException {
+        when(mLockoutTracker.getLockoutModeForUser(anyInt())).thenReturn(
+                LockoutTracker.LOCKOUT_PERMANENT);
+
+        final FaceAuthenticationClient client = createClientWithLockoutTracker(mLockoutTracker);
+        client.start(mCallback);
+
+        verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(),
+                eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT), anyInt());
+        verify(mHal, never()).authenticate(anyInt());
+    }
+
     private FaceAuthenticationClient createClient() throws RemoteException {
         return createClient(2 /* version */, mClientMonitorCallbackConverter,
-                false /* allowBackgroundAuthentication */);
+                false /* allowBackgroundAuthentication */,
+                null /* lockoutTracker */);
     }
 
     private FaceAuthenticationClient createClientWithNullListener() throws RemoteException {
         return createClient(2 /* version */, null /* listener */,
-                true /* allowBackgroundAuthentication */);
+                true /* allowBackgroundAuthentication */,
+                null /* lockoutTracker */);
     }
 
     private FaceAuthenticationClient createClient(int version) throws RemoteException {
         return createClient(version, mClientMonitorCallbackConverter,
-                false /* allowBackgroundAuthentication */);
+                false /* allowBackgroundAuthentication */,
+                null /* lockoutTracker */);
+    }
+
+    private FaceAuthenticationClient createClientWithLockoutTracker(LockoutTracker lockoutTracker)
+            throws RemoteException {
+        return createClient(0 /* version */,
+                mClientMonitorCallbackConverter,
+                true /* allowBackgroundAuthentication */,
+                lockoutTracker);
     }
 
     private FaceAuthenticationClient createClient(int version,
             ClientMonitorCallbackConverter listener,
-            boolean allowBackgroundAuthentication) throws RemoteException {
+            boolean allowBackgroundAuthentication,
+            LockoutTracker lockoutTracker) throws RemoteException {
         when(mHal.getInterfaceVersion()).thenReturn(version);
 
-        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
         final FaceAuthenticateOptions options = new FaceAuthenticateOptions.Builder()
                 .setOpPackageName("test-owner")
                 .setUserId(USER_ID)
@@ -270,7 +310,7 @@
                 false /* restricted */, options, 4 /* cookie */,
                 false /* requireConfirmation */,
                 mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
-                mUsageStats, null /* mLockoutCache */, allowBackgroundAuthentication,
+                mUsageStats, lockoutTracker, allowBackgroundAuthentication,
                 null /* sensorPrivacyManager */, 0 /* biometricStrength */) {
             @Override
             protected ActivityTaskManager getActivityTaskManager() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
index ade3e82..fbf0e13 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
@@ -87,7 +87,7 @@
     @Mock
     private ClientMonitorCallback mCallback;
     @Mock
-    private Sensor.HalSessionCallback mHalSessionCallback;
+    private AidlResponseHandler mAidlResponseHandler;
     @Captor
     private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
     @Captor
@@ -170,7 +170,7 @@
     private FaceDetectClient createClient(int version) throws RemoteException {
         when(mHal.getInterfaceVersion()).thenReturn(version);
 
-        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
         return new FaceDetectClient(mContext, () -> aidl, mToken,
                 99 /* requestId */, mClientMonitorCallbackConverter,
                 new FaceAuthenticateOptions.Builder()
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
index 54d116f..128f314 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
@@ -83,7 +83,7 @@
     @Mock
     private ClientMonitorCallback mCallback;
     @Mock
-    private Sensor.HalSessionCallback mHalSessionCallback;
+    private AidlResponseHandler mAidlResponseHandler;
     @Captor
     private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
     @Captor
@@ -150,7 +150,7 @@
     private FaceEnrollClient createClient(int version) throws RemoteException {
         when(mHal.getInterfaceVersion()).thenReturn(version);
 
-        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
         return new FaceEnrollClient(mContext, () -> aidl, mToken, mClientMonitorCallbackConverter,
                 USER_ID, HAT, "com.foo.bar", 44 /* requestId */,
                 mUtils, new int[0] /* disabledFeatures */, 6 /* timeoutSec */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java
new file mode 100644
index 0000000..c8bfaa9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.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 com.android.server.biometrics.sensors.face.aidl;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+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.ClientMonitorCallbackConverter;
+
+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 FaceGenerateChallengeClientTest {
+    private static final String TAG = "FaceGenerateChallengeClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+    private static final long CHALLENGE = 200;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mListener;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+
+    private FaceGenerateChallengeClient mClient;
+
+    @Before
+    public void setUp() throws RemoteException {
+        when(mAidlSession.getSession()).thenReturn(mSession);
+        doAnswer(invocation -> {
+            mClient.onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE);
+            return null;
+        }).when(mSession).generateChallenge();
+    }
+
+    @Test
+    public void generateChallenge() throws RemoteException {
+        createClient(mListener);
+        mClient.start(mCallback);
+
+        verify(mListener).onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE);
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void generateChallenge_nullListener() {
+        createClient(null);
+        mClient.start(mCallback);
+
+        verify(mCallback).onClientFinished(mClient, false);
+    }
+
+    private void createClient(ClientMonitorCallbackConverter listener) {
+        mClient = new FaceGenerateChallengeClient(mContext, () -> mAidlSession, mToken, listener,
+                USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java
new file mode 100644
index 0000000..9d0c84e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java
@@ -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.server.biometrics.sensors.face.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableContext;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+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.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FaceGetFeatureClientTest {
+    private static final String TAG = "FaceGetFeatureClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mListener;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+
+    private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION;
+    private FaceGetFeatureClient mClient;
+
+    @Before
+    public void setUp() throws RemoteException {
+        mClient = new FaceGetFeatureClient(mContext, () -> mAidlSession, mToken, mListener,
+                USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, mFeature);
+
+        when(mAidlSession.getSession()).thenReturn(mSession);
+        doAnswer(invocation -> {
+            mClient.onFeatureGet(true, new byte[]{
+                    AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)});
+            return null;
+        }).when(mSession).getFeatures();
+    }
+
+    @Test
+    public void getFeature() throws RemoteException {
+        ArgumentCaptor<int[]> featuresToSend = ArgumentCaptor.forClass(int[].class);
+        ArgumentCaptor<boolean[]> featureState = ArgumentCaptor.forClass(boolean[].class);
+        mClient.start(mCallback);
+
+        verify(mListener).onFeatureGet(eq(true), featuresToSend.capture(),
+                featureState.capture());
+        assertThat(featuresToSend.getValue()).asList().containsExactlyElementsIn(List.of(mFeature));
+        assertThat(featureState.getValue()).asList().containsExactlyElementsIn(List.of(true));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java
new file mode 100644
index 0000000..1b4c017
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.face.Face;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Presubmit
+@SmallTest
+public class FaceInternalCleanupClientTest {
+    private static final String TAG = "FaceInternalCleanupClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private BiometricUtils<Face> mBiometricUtils;
+    @Mock
+    private Map<Integer, Long> mAuthenticatorIds;
+
+    private final List<Face> mEnrolledList = new ArrayList<>();
+    private final int mBiometricId = 1;
+    private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */);
+    private FaceInternalCleanupClient mClient;
+    private List<Integer> mAddedIds;
+
+    @Before
+    public void setUp() throws RemoteException {
+        when(mAidlSession.getSession()).thenReturn(mSession);
+
+        mEnrolledList.add(mFace);
+        mAddedIds = new ArrayList<>();
+        mClient = new FaceInternalCleanupClient(mContext, () -> mAidlSession, USER_ID, TAG,
+                SENSOR_ID, mBiometricLogger, mBiometricContext, mBiometricUtils,
+                mAuthenticatorIds) {
+            @Override
+            protected void onAddUnknownTemplate(int userId,
+                    @NonNull BiometricAuthenticator.Identifier identifier) {
+                mAddedIds.add(identifier.getBiometricId());
+            }
+        };
+    }
+
+    @Test
+    public void removesUnknownTemplate() throws Exception {
+        final List<Face> templates = List.of(
+                new Face("one", 1, 1),
+                new Face("two", 2, 1)
+        );
+        mClient.start(mCallback);
+        for (int i = templates.size() - 1; i >= 0; i--) {
+            mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
+        }
+        for (int i = templates.size() - 1; i >= 0; i--) {
+            mClient.getCurrentRemoveClient().onRemoved(templates.get(i), 0);
+        }
+
+        assertThat(mAddedIds).isEmpty();
+        final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class);
+
+        verify(mSession, times(2)).removeEnrollments(captor.capture());
+        assertThat(captor.getAllValues().stream()
+                .flatMap(x -> Arrays.stream(x).boxed())
+                .collect(Collectors.toList()))
+                .containsExactly(1, 2);
+        verify(mCallback).onClientFinished(eq(mClient), eq(true));
+    }
+
+    @Test
+    public void addsUnknownTemplateWhenVirtualIsEnabled() throws Exception {
+        mClient.setFavorHalEnrollments();
+        final List<Face> templates = List.of(
+                new Face("one", 1, 1),
+                new Face("two", 2, 1)
+        );
+        mClient.start(mCallback);
+        for (int i = templates.size() - 1; i >= 0; i--) {
+            mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
+        }
+
+        assertThat(mAddedIds).containsExactly(1, 2);
+        verify(mSession, never()).removeEnrollments(any());
+        verify(mCallback).onClientFinished(eq(mClient), eq(true));
+    }
+
+    @Test
+    public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() {
+        final List<Face> templates = List.of(
+                new Face("one", 1, 1),
+                new Face("two", 2, 1),
+                new Face("three", 3, 1)
+        );
+        mClient.start(mCallback);
+        for (int i = templates.size() - 1; i >= 0; i--) {
+            mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
+        }
+
+        // The first template is removed after enumeration
+        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(2);
+
+        // Simulate finishing the removal of the first template.
+        // |remaining| is 0 because one FaceRemovalClient is associated with only one
+        // biometrics ID.
+        mClient.getCurrentRemoveClient().onRemoved(templates.get(0), 0);
+
+        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1);
+
+        // Simulate finishing the removal of the second template.
+        mClient.getCurrentRemoveClient().onRemoved(templates.get(1), 0);
+
+        assertThat(mClient.getUnknownHALTemplates()).isEmpty();
+    }
+
+    @Test
+    public void noUnknownTemplates() throws RemoteException {
+        mClient.start(mCallback);
+        mClient.getCurrentEnumerateClient().onEnumerationResult(null, 0);
+
+        verify(mSession).enumerateEnrollments();
+        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+        verify(mSession, never()).removeEnrollments(any());
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java
new file mode 100644
index 0000000..8d74fd1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.face.Face;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+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.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FaceInternalEnumerateClientTest {
+    private static final String TAG = "FaceInternalEnumerateClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private BiometricUtils<Face> mBiometricUtils;
+
+    private final int mBiometricId = 1;
+    private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */);
+    private FaceInternalEnumerateClient mClient;
+
+    @Before
+    public void setUp() {
+        when(mAidlSession.getSession()).thenReturn(mSession);
+
+        final List<Face> enrolled = new ArrayList<>();
+        enrolled.add(mFace);
+        mClient = new FaceInternalEnumerateClient(mContext, () -> mAidlSession, mToken, USER_ID,
+                TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, mBiometricContext);
+    }
+
+    @Test
+    public void internalCleanupClient_noTemplatesRemaining() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onEnumerationResult(mFace, 0);
+            return null;
+        }).when(mSession).enumerateEnrollments();
+
+        mClient.start(mCallback);
+
+        verify(mSession).enumerateEnrollments();
+        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+        verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void internalCleanupClient_nullIdentifier_remainingOne() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onEnumerationResult(null, 1);
+            return null;
+        }).when(mSession).enumerateEnrollments();
+
+        mClient.start(mCallback);
+
+        verify(mSession).enumerateEnrollments();
+        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+        verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
+        verify(mCallback, never()).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void internalCleanupClient_nullIdentifier_noTemplatesRemaining() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onEnumerationResult(null, 0);
+            return null;
+        }).when(mSession).enumerateEnrollments();
+
+        mClient.start(mCallback);
+
+        verify(mSession).enumerateEnrollments();
+        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+        verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId);
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void internalCleanupClient_templatesRemaining() throws RemoteException {
+        final Face identifier = new Face("face", 2, 1);
+        doAnswer(invocation -> {
+            mClient.onEnumerationResult(identifier, 1);
+            return null;
+        }).when(mSession).enumerateEnrollments();
+
+        mClient.start(mCallback);
+
+        verify(mSession).enumerateEnrollments();
+        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1);
+        verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
+        verify(mCallback, never()).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void internalCleanupClient_differentIdentifier_noTemplatesRemaining()
+            throws RemoteException {
+        final Face identifier = new Face("face", 2, 1);
+        doAnswer(invocation -> {
+            mClient.onEnumerationResult(identifier, 0);
+            return null;
+        }).when(mSession).enumerateEnrollments();
+
+        mClient.start(mCallback);
+
+        verify(mSession).enumerateEnrollments();
+        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1);
+        verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId);
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
index 76a5acc..1d9e933 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
@@ -76,7 +76,7 @@
     @Mock
     private ClientMonitorCallback mCallback;
     @Mock
-    private Sensor.HalSessionCallback mHalSessionCallback;
+    private AidlResponseHandler mAidlResponseHandler;
     @Mock
     private BiometricUtils<Face> mUtils;
     @Mock
@@ -115,7 +115,7 @@
 
     private FaceRemovalClient createClient(int version, int[] biometricIds) throws RemoteException {
         when(mHal.getInterfaceVersion()).thenReturn(version);
-        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
         return new FaceRemovalClient(mContext, () -> aidl, mToken,
                 mClientMonitorCallbackConverter, biometricIds, USER_ID,
                 "own-it", mUtils /* utils */, 5 /* sensorId */, mBiometricLogger, mBiometricContext,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java
new file mode 100644
index 0000000..dbbd69b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.aidl;
+
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
+
+import static org.mockito.ArgumentMatchers.any;
+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.content.Context;
+import android.hardware.biometrics.face.ISession;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.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.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.LockoutCache;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+
+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 FaceResetLockoutClientTest {
+    private static final String TAG = "FaceResetLockoutClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    private final byte[] mHardwareAuthToken = new byte[69];
+    @Mock
+    private LockoutCache mLockoutTracker;
+    @Mock
+    private LockoutResetDispatcher mLockoutResetDispatcher;
+    @Mock
+    private AuthSessionCoordinator mAuthSessionCoordinator;
+
+    private FaceResetLockoutClient mClient;
+
+    @Before
+    public void setUp() {
+        mClient = new FaceResetLockoutClient(mContext, () -> mAidlSession, USER_ID, TAG, SENSOR_ID,
+                mBiometricLogger, mBiometricContext, mHardwareAuthToken, mLockoutTracker,
+                mLockoutResetDispatcher, BIOMETRIC_STRONG);
+
+        when(mAidlSession.getSession()).thenReturn(mSession);
+        when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+    }
+
+    @Test
+    public void testResetLockout_onLockoutCleared() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onLockoutCleared();
+            return null;
+        }).when(mSession).resetLockout(any());
+        mClient.start(mCallback);
+
+        verify(mSession).resetLockout(any());
+        verify(mAuthSessionCoordinator).resetLockoutFor(USER_ID, BIOMETRIC_STRONG, -1);
+        verify(mLockoutTracker).setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_NONE);
+        verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(SENSOR_ID);
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void testResetLockout_onError() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onError(0, 0);
+            return null;
+        }).when(mSession).resetLockout(any());
+        mClient.start(mCallback);
+
+        verify(mSession).resetLockout(any());
+        verify(mAuthSessionCoordinator, never()).resetLockoutFor(USER_ID,
+                BIOMETRIC_STRONG, -1);
+        verify(mLockoutTracker, never()).setLockoutModeForUser(USER_ID,
+                LockoutTracker.LOCKOUT_NONE);
+        verify(mLockoutResetDispatcher, never()).notifyLockoutResetCallbacks(SENSOR_ID);
+        verify(mCallback).onClientFinished(mClient, false);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java
new file mode 100644
index 0000000..fb5502a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.aidl;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+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 FaceRevokeChallengeClientTest {
+    private static final String TAG = "FaceRevokeChallengeClientTest";
+    private static final long CHALLENGE = 200L;
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+
+    private FaceRevokeChallengeClient mClient;
+
+    @Before
+    public void setUp() {
+        when(mAidlSession.getSession()).thenReturn(mSession);
+
+        mClient = new FaceRevokeChallengeClient(mContext, () -> mAidlSession, mToken, USER_ID, TAG,
+                SENSOR_ID, mBiometricLogger, mBiometricContext, CHALLENGE);
+    }
+
+    @Test
+    public void revokeChallenge() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onChallengeRevoked(SENSOR_ID, USER_ID, CHALLENGE);
+            return null;
+        }).when(mSession).revokeChallenge(CHALLENGE);
+        mClient.start(mCallback);
+
+        verify(mSession).revokeChallenge(CHALLENGE);
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java
new file mode 100644
index 0000000..eb8cc9c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableContext;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+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.ClientMonitorCallbackConverter;
+
+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 FaceSetFeatureClientTest {
+    private static final String TAG = "FaceSetFeatureClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mListener;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+
+    private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION;
+    private final boolean mEnabled = true;
+    private final byte[] mHardwareAuthToken = new byte[69];
+    private FaceSetFeatureClient mClient;
+    TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
+    @Before
+    public void setUp() {
+        when(mAidlSession.getSession()).thenReturn(mSession);
+
+        mClient = new FaceSetFeatureClient(mContext, () -> mAidlSession, mToken, mListener, USER_ID,
+                TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, mFeature, mEnabled,
+                mHardwareAuthToken);
+    }
+
+    @Test
+    public void setFeature_onFeatureSet() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onFeatureSet(true);
+            return null;
+        }).when(mSession).setFeature(any(),
+                eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), eq(mEnabled));
+        mClient.start(mCallback);
+
+        verify(mSession).setFeature(any(),
+                eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)),
+                eq(mEnabled));
+        verify(mListener).onFeatureSet(true, mFeature);
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void setFeature_onError() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onError(0, 0);
+            return null;
+        }).when(mSession).setFeature(any(),
+                eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)),
+                eq(mEnabled));
+        mClient.start(mCallback);
+
+        verify(mSession).setFeature(any(),
+                eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)),
+                eq(mEnabled));
+        verify(mListener).onFeatureSet(false, mFeature);
+        verify(mCallback).onClientFinished(mClient, false);
+    }
+}
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 be9f52e..7a293e8 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
@@ -74,7 +74,7 @@
     @Mock
     private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
     @Mock
-    private Sensor.HalSessionCallback.Callback mHalSessionCallback;
+    private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
     @Mock
     private LockoutResetDispatcher mLockoutResetDispatcher;
     @Mock
@@ -94,7 +94,7 @@
     private final LockoutCache mLockoutCache = new LockoutCache();
 
     private UserAwareBiometricScheduler mScheduler;
-    private Sensor.HalSessionCallback mHalCallback;
+    private AidlResponseHandler mHalCallback;
 
     @Before
     public void setUp() {
@@ -111,10 +111,9 @@
                 mBiometricService,
                 () -> USER_ID,
                 mUserSwitchCallback);
-        mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
-                TAG, mScheduler, SENSOR_ID,
-                USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
-                mHalSessionCallback);
+        mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
+                mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
+                mHardwareUnavailableCallback);
     }
 
     @Test
@@ -153,11 +152,11 @@
         sensorProps.commonProps.sensorId = 1;
         final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
                 sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
-                sensorProps.commonProps.maxEnrollmentsPerUser, null,
+                sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */,
                 sensorProps.sensorType, sensorProps.supportsDetectInteraction,
                 sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
-        final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null,
-                internalProp, mLockoutResetDispatcher, mBiometricContext);
+        final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext,
+                null /* handler */, internalProp, mLockoutResetDispatcher, mBiometricContext);
 
         mScheduler.reset();
 
@@ -181,7 +180,7 @@
         sensorProps.commonProps.sensorId = 1;
         final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
                 sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
-                sensorProps.commonProps.maxEnrollmentsPerUser, null,
+                sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */,
                 sensorProps.sensorType, sensorProps.supportsDetectInteraction,
                 sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
         final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null,
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/AidlToHidlAdapterTest.java
new file mode 100644
index 0000000..9a40e8a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java
@@ -0,0 +1,342 @@
+/*
+ * 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.AidlToHidlAdapter.ENROLL_TIMEOUT_SEC;
+import static com.android.server.biometrics.sensors.face.hidl.FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.face.EnrollmentType;
+import android.hardware.biometrics.face.Feature;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.biometrics.face.V1_0.OptionalBool;
+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.FaceManager;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.hardware.keymaster.Timestamp;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableContext;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlConversionUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class AidlToHidlAdapterTest {
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private IBiometricsFace mSession;
+    @Mock
+    FaceManager mFaceManager;
+    @Mock
+    private AidlResponseHandler mAidlResponseHandler;
+    @Mock
+    private HardwareAuthToken mHardwareAuthToken;
+    @Mock
+    private Clock mClock;
+
+    private final long mChallenge = 100L;
+    private AidlToHidlAdapter mAidlToHidlAdapter;
+    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 {
+        TestableContext testableContext = new TestableContext(
+                InstrumentationRegistry.getInstrumentation().getContext());
+        testableContext.addMockSystemService(FaceManager.class, mFaceManager);
+        mAidlToHidlAdapter = new AidlToHidlAdapter(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);
+        when(mFaceManager.getEnrolledFaces(anyInt())).thenReturn(List.of(mFace));
+    }
+
+    @Test
+    public void testGenerateChallengeCache() throws RemoteException {
+        verify(mSession).setCallback(any());
+
+        final ArgumentCaptor<Long> challengeCaptor = ArgumentCaptor.forClass(Long.class);
+
+        mAidlToHidlAdapter.generateChallenge();
+
+        verify(mSession).generateChallenge(CHALLENGE_TIMEOUT_SEC);
+        verify(mAidlResponseHandler).onChallengeGenerated(challengeCaptor.capture());
+        assertThat(challengeCaptor.getValue()).isEqualTo(mChallenge);
+
+        forwardTime(10 /* seconds */);
+        mAidlToHidlAdapter.generateChallenge();
+        forwardTime(20 /* seconds */);
+        mAidlToHidlAdapter.generateChallenge();
+
+        //Confirms that the challenge is cached and the hal method is not called again
+        verifyNoMoreInteractions(mSession);
+        verify(mAidlResponseHandler, times(3))
+                .onChallengeGenerated(mChallenge);
+
+        forwardTime(60 /* seconds */);
+        mAidlToHidlAdapter.generateChallenge();
+
+        //HAL method called after challenge has timed out
+        verify(mSession, times(2)).generateChallenge(CHALLENGE_TIMEOUT_SEC);
+    }
+
+    @Test
+    public void testRevokeChallenge_waitsUntilEmpty() throws RemoteException {
+        for (int i = 0; i < 3; i++) {
+            mAidlToHidlAdapter.generateChallenge();
+            forwardTime(10 /* seconds */);
+        }
+        for (int i = 0; i < 3; i++) {
+            mAidlToHidlAdapter.revokeChallenge(0);
+            forwardTime((i + 1) * 10 /* seconds */);
+        }
+
+        verify(mSession).revokeChallenge();
+    }
+
+    @Test
+    public void testRevokeChallenge_timeout() throws RemoteException {
+        mAidlToHidlAdapter.generateChallenge();
+        mAidlToHidlAdapter.generateChallenge();
+        forwardTime(700);
+        mAidlToHidlAdapter.generateChallenge();
+        mAidlToHidlAdapter.revokeChallenge(0);
+
+        verify(mSession).revokeChallenge();
+    }
+
+    @Test
+    public void testEnroll() throws RemoteException {
+        ICancellationSignal cancellationSignal = mAidlToHidlAdapter.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());
+
+        ArrayList<Integer> features = featureCaptor.getValue();
+
+        assertThat(features).containsExactly(
+                AidlConversionUtils.convertAidlToFrameworkFeature(mFeatures[0]));
+
+        cancellationSignal.cancel();
+
+        verify(mSession).cancel();
+    }
+
+    @Test
+    public void testAuthenticate() throws RemoteException {
+        final int operationId = 2;
+        ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId);
+
+        verify(mSession).authenticate(operationId);
+
+        cancellationSignal.cancel();
+
+        verify(mSession).cancel();
+    }
+
+    @Test
+    public void testDetectInteraction() throws RemoteException {
+        ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction();
+
+        verify(mSession).authenticate(0);
+
+        cancellationSignal.cancel();
+
+        verify(mSession).cancel();
+    }
+
+    @Test
+    public void testEnumerateEnrollments() throws RemoteException {
+        mAidlToHidlAdapter.enumerateEnrollments();
+
+        verify(mSession).enumerate();
+    }
+
+    @Test
+    public void testRemoveEnrollment() throws RemoteException {
+        final int[] enrollments = new int[]{1};
+        mAidlToHidlAdapter.removeEnrollments(enrollments);
+
+        verify(mSession).remove(enrollments[0]);
+    }
+
+    @Test
+    public void testGetFeatures_onResultSuccess() throws RemoteException {
+        final OptionalBool result = new OptionalBool();
+        result.status = Status.OK;
+        result.value = true;
+        ArgumentCaptor<byte[]> featureRetrieved = ArgumentCaptor.forClass(byte[].class);
+
+        when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
+
+        mAidlToHidlAdapter.setFeature(mFeature);
+        mAidlToHidlAdapter.getFeatures();
+
+        verify(mSession).getFeature(eq(mFeature), anyInt());
+        verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture());
+        assertThat(featureRetrieved.getValue()[0]).isEqualTo(
+                AidlConversionUtils.convertFrameworkToAidlFeature(mFeature));
+    }
+
+    @Test
+    public void testGetFeatures_onResultFailed() throws RemoteException {
+        final OptionalBool result = new OptionalBool();
+        result.status = Status.OK;
+        result.value = false;
+        ArgumentCaptor<byte[]> featureRetrieved = ArgumentCaptor.forClass(byte[].class);
+
+        when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
+
+        mAidlToHidlAdapter.setFeature(mFeature);
+        mAidlToHidlAdapter.getFeatures();
+
+        verify(mSession).getFeature(eq(mFeature), anyInt());
+        verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture());
+        assertThat(featureRetrieved.getValue().length).isEqualTo(0);
+    }
+
+    @Test
+    public void testGetFeatures_onStatusFailed() throws RemoteException {
+        final OptionalBool result = new OptionalBool();
+        result.status = Status.INTERNAL_ERROR;
+        result.value = false;
+
+        when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
+
+        mAidlToHidlAdapter.setFeature(mFeature);
+        mAidlToHidlAdapter.getFeatures();
+
+        verify(mSession).getFeature(eq(mFeature), anyInt());
+        verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any());
+        verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0);
+    }
+
+    @Test
+    public void testGetFeatures_featureNotSet() throws RemoteException {
+        mAidlToHidlAdapter.getFeatures();
+
+        verify(mSession, never()).getFeature(eq(mFeature), anyInt());
+        verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any());
+    }
+
+    @Test
+    public void testSetFeatureSuccessful() throws RemoteException {
+        byte feature = Feature.REQUIRE_ATTENTION;
+        boolean enabled = true;
+
+        when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt())).thenReturn(Status.OK);
+
+        mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled);
+
+        verify(mAidlResponseHandler).onFeatureSet(feature);
+    }
+
+    @Test
+    public void testSetFeatureFailed() throws RemoteException {
+        byte feature = Feature.REQUIRE_ATTENTION;
+        boolean enabled = true;
+
+        when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt()))
+                .thenReturn(Status.INTERNAL_ERROR);
+
+        mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled);
+
+        verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN,
+                0 /* vendorCode */);
+    }
+
+    @Test
+    public void testGetAuthenticatorId() throws RemoteException {
+        final long authenticatorId = 2L;
+        final OptionalUint64 result = new OptionalUint64();
+        result.status = Status.OK;
+        result.value = authenticatorId;
+
+        when(mSession.getAuthenticatorId()).thenReturn(result);
+
+        mAidlToHidlAdapter.getAuthenticatorId();
+
+        verify(mSession).getAuthenticatorId();
+        verify(mAidlResponseHandler).onAuthenticatorIdRetrieved(authenticatorId);
+    }
+
+    @Test
+    public void testResetLockout() throws RemoteException {
+        mAidlToHidlAdapter.resetLockout(mHardwareAuthToken);
+
+        ArgumentCaptor<ArrayList> hatCaptor = ArgumentCaptor.forClass(ArrayList.class);
+
+        verify(mSession).resetLockout(hatCaptor.capture());
+
+        assertThat(hatCaptor.getValue()).containsExactlyElementsIn(processHAT(mHardwareAuthToken));
+    }
+
+    private ArrayList<Byte> processHAT(HardwareAuthToken hat) {
+        ArrayList<Byte> hardwareAuthToken = new ArrayList<>();
+        for (byte b : HardwareAuthTokenUtils.toByteArray(hat)) {
+            hardwareAuthToken.add(b);
+        }
+        return hardwareAuthToken;
+    }
+
+    private void forwardTime(long seconds) {
+        when(mClock.millis()).thenReturn(seconds * 1000);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 8a11e31..79a528c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -53,11 +53,15 @@
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 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.testing.TestableContext;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.CallbackWithProbe;
@@ -66,6 +70,7 @@
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutTracker;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -102,6 +107,9 @@
             InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Mock
     private ISession mHal;
@@ -124,7 +132,7 @@
     @Mock
     private ClientMonitorCallback mCallback;
     @Mock
-    private Sensor.HalSessionCallback mHalSessionCallback;
+    private AidlResponseHandler mAidlResponseHandler;
     @Mock
     private ActivityTaskManager mActivityTaskManager;
     @Mock
@@ -135,6 +143,8 @@
     private AuthSessionCoordinator mAuthSessionCoordinator;
     @Mock
     private Clock mClock;
+    @Mock
+    private LockoutTracker mLockoutTracker;
     @Captor
     private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
     @Captor
@@ -425,37 +435,65 @@
         verify(mCallback).onClientFinished(client, true);
     }
 
+    @Test
+    public void testLockoutTracker_authSuccess() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient(1 /* version */,
+                true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter,
+                mLockoutTracker);
+        client.start(mCallback);
+        client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+                2 /* deviceId */), true /* authenticated */, new ArrayList<>());
+
+        verify(mLockoutTracker).resetFailedAttemptsForUser(true, USER_ID);
+        verify(mLockoutTracker, never()).addFailedAttemptForUser(anyInt());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testLockoutTracker_authFailed() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient(1 /* version */,
+                true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter,
+                mLockoutTracker);
+        client.start(mCallback);
+        client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+                2 /* deviceId */), false /* authenticated */, new ArrayList<>());
+
+        verify(mLockoutTracker, never()).resetFailedAttemptsForUser(anyBoolean(), anyInt());
+        verify(mLockoutTracker).addFailedAttemptForUser(USER_ID);
+    }
+
     private FingerprintAuthenticationClient createClient() throws RemoteException {
         return createClient(100 /* version */, true /* allowBackgroundAuthentication */,
-                mClientMonitorCallbackConverter);
+                mClientMonitorCallbackConverter, null);
     }
 
     private FingerprintAuthenticationClient createClientWithoutBackgroundAuth()
             throws RemoteException {
         return createClient(100 /* version */, false /* allowBackgroundAuthentication */,
-                mClientMonitorCallbackConverter);
+                mClientMonitorCallbackConverter, null);
     }
 
     private FingerprintAuthenticationClient createClient(int version) throws RemoteException {
         return createClient(version, true /* allowBackgroundAuthentication */,
-                mClientMonitorCallbackConverter);
+                mClientMonitorCallbackConverter, null);
     }
 
     private FingerprintAuthenticationClient createClientWithNullListener() throws RemoteException {
         return createClient(100 /* version */, true /* allowBackgroundAuthentication */,
-                null /* listener */);
+                null, /* listener */null);
     }
 
     private FingerprintAuthenticationClient createClient(int version,
-            boolean allowBackgroundAuthentication, ClientMonitorCallbackConverter listener)
+            boolean allowBackgroundAuthentication, ClientMonitorCallbackConverter listener,
+            LockoutTracker lockoutTracker)
             throws RemoteException {
         when(mHal.getInterfaceVersion()).thenReturn(version);
 
-        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
         final FingerprintAuthenticateOptions options = new FingerprintAuthenticateOptions.Builder()
                 .setOpPackageName("test-owner")
-                .setUserId(5)
-                .setSensorId(9)
+                .setUserId(USER_ID)
+                .setSensorId(SENSOR_ID)
                 .build();
         return new FingerprintAuthenticationClient(mContext, () -> aidl, mToken,
                 REQUEST_ID, listener, OP_ID,
@@ -463,10 +501,11 @@
                 false /* requireConfirmation */,
                 mBiometricLogger, mBiometricContext,
                 true /* isStrongBiometric */,
-                null /* taskStackListener */, null /* lockoutCache */,
+                null /* taskStackListener */,
                 mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication,
                 mSensorProps,
-                new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) {
+                new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock,
+                lockoutTracker) {
             @Override
             protected ActivityTaskManager getActivityTaskManager() {
                 return mActivityTaskManager;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index 78d3a9d..a467c84 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -83,7 +83,7 @@
     @Mock
     private ClientMonitorCallback mCallback;
     @Mock
-    private Sensor.HalSessionCallback mHalSessionCallback;
+    private AidlResponseHandler mAidlResponseHandler;
     @Captor
     private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
     @Captor
@@ -150,7 +150,7 @@
 
     @Test
     public void testWhenListenerIsNull() {
-        final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mHalSessionCallback);
+        final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mAidlResponseHandler);
         final FingerprintDetectClient client =  new FingerprintDetectClient(mContext, () -> aidl,
                 mToken, 6 /* requestId */, null /* listener */,
                 new FingerprintAuthenticateOptions.Builder()
@@ -173,7 +173,7 @@
     private FingerprintDetectClient createClient(int version) throws RemoteException {
         when(mHal.getInterfaceVersion()).thenReturn(version);
 
-        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
         return new FingerprintDetectClient(mContext, () -> aidl, mToken,
                 6 /* requestId */, mClientMonitorCallbackConverter,
                 new FingerprintAuthenticateOptions.Builder()
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index ef25380..c7eb1db 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -102,7 +102,7 @@
     @Mock
     private ClientMonitorCallback mCallback;
     @Mock
-    private Sensor.HalSessionCallback mHalSessionCallback;
+    private AidlResponseHandler mAidlResponseHandler;
     @Mock
     private Probe mLuxProbe;
     @Captor
@@ -291,7 +291,7 @@
     private FingerprintEnrollClient createClient(int version) throws RemoteException {
         when(mHal.getInterfaceVersion()).thenReturn(version);
 
-        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
         return new FingerprintEnrollClient(mContext, () -> aidl, mToken, REQUEST_ID,
         mClientMonitorCallbackConverter, 0 /* userId */,
         HAT, "owner", mBiometricUtils, 8 /* sensorId */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java
new file mode 100644
index 0000000..8409619
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.aidl;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+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.ClientMonitorCallbackConverter;
+
+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 FingerprintGenerateChallengeClientTest {
+    private static final String TAG = "FingerprintGenerateChallengeClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+    private static final long CHALLENGE = 200;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mListener;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+
+    private FingerprintGenerateChallengeClient mClient;
+
+    @Before
+    public void setUp() {
+        when(mAidlSession.getSession()).thenReturn(mSession);
+
+        mClient = new FingerprintGenerateChallengeClient(mContext, () -> mAidlSession, mToken,
+                mListener, USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext);
+    }
+
+    @Test
+    public void generateChallenge() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE);
+            return null;
+        }).when(mSession).generateChallenge();
+        mClient.start(mCallback);
+
+        verify(mSession).generateChallenge();
+        verify(mListener).onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE);
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
index 5806443..c9482ce 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
@@ -28,6 +28,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.fingerprint.Fingerprint;
+import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.testing.TestableContext;
 
@@ -41,7 +42,6 @@
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -70,9 +70,9 @@
             InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
 
     @Mock
-    private AidlSession mAidlSession;
+    ISession mSession;
     @Mock
-    private ISession mSession;
+    private AidlSession mAidlSession;
     @Mock
     private BiometricLogger mLogger;
     @Mock
@@ -87,15 +87,16 @@
 
     @Before
     public void setup() {
-        when(mAidlSession.getSession()).thenReturn(mSession);
         mAddedIds = new ArrayList<>();
+
+        when(mAidlSession.getSession()).thenReturn(mSession);
     }
 
-    @Ignore("TODO(b/229015801): verify cleanup behavior")
     @Test
     public void removesUnknownTemplate() throws Exception {
         mClient = createClient();
 
+        final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class);
         final List<Fingerprint> templates = List.of(
                 new Fingerprint("one", 1, 1),
                 new Fingerprint("two", 2, 1)
@@ -108,8 +109,8 @@
             mClient.getCurrentRemoveClient().onRemoved(templates.get(i), 0);
         }
 
+        verify(mSession).enumerateEnrollments();
         assertThat(mAddedIds).isEmpty();
-        final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class);
         verify(mSession, times(2)).removeEnrollments(captor.capture());
         assertThat(captor.getAllValues().stream()
                 .flatMap(x -> Arrays.stream(x).boxed())
@@ -132,13 +133,15 @@
             mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
         }
 
+        verify(mSession).enumerateEnrollments();
         assertThat(mAddedIds).containsExactly(1, 2);
         verify(mSession, never()).removeEnrollments(any());
         verify(mCallback).onClientFinished(eq(mClient), eq(true));
     }
 
     @Test
-    public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() {
+    public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled()
+            throws RemoteException {
         mClient = createClient();
 
         final List<Fingerprint> templates = List.of(
@@ -150,6 +153,8 @@
         for (int i = templates.size() - 1; i >= 0; i--) {
             mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
         }
+
+        verify(mSession).enumerateEnrollments();
         // The first template is removed after enumeration
         assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(2);
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java
new file mode 100644
index 0000000..723f916
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java
@@ -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.server.biometrics.sensors.fingerprint.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.Fingerprint;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+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.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@Presubmit
+@SmallTest
+public class FingerprintInternalEnumerateClientTest {
+    private static final String TAG = "FingerprintInternalEnumerateClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private BiometricUtils<Fingerprint> mBiometricUtils;
+
+    private FingerprintInternalEnumerateClient mClient;
+
+    @Before
+    public void setUp() {
+        when(mAidlSession.getSession()).thenReturn(mSession);
+
+        List<Fingerprint> enrolled = new ArrayList<>();
+        enrolled.add(new Fingerprint("one", 1, 1));
+        mClient = new FingerprintInternalEnumerateClient(mContext, () -> mAidlSession, mToken,
+                USER_ID, TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger,
+                mBiometricContext);
+    }
+
+    @Test
+    public void internalEnumerate_unknownTemplates() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onEnumerationResult(new Fingerprint("two", 2, 1), 1);
+            mClient.onEnumerationResult(new Fingerprint("three", 3, 1), 0);
+            return null;
+        }).when(mSession).enumerateEnrollments();
+        mClient.start(mCallback);
+
+        verify(mSession).enumerateEnrollments();
+        assertThat(mClient.getUnknownHALTemplates().stream()
+                .flatMap(x -> Stream.of(x.getBiometricId()))
+                .collect(Collectors.toList())).containsExactly(2, 3);
+        verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, 1);
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void internalEnumerate_noUnknownTemplates() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onEnumerationResult(new Fingerprint("one", 1, 1), 0);
+            return null;
+        }).when(mSession).enumerateEnrollments();
+        mClient.start(mCallback);
+
+        verify(mSession).enumerateEnrollments();
+        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+        verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java
new file mode 100644
index 0000000..64f07e2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.aidl;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.Fingerprint;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+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.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+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.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@Presubmit
+@SmallTest
+public class FingerprintRemovalClientTest {
+    private static final String TAG = "FingerprintRemovalClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallbackConverter mListener;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private BiometricUtils<Fingerprint> mBiometricUtils;
+    @Mock
+    private Map<Integer, Long> mAuthenticatorIds;
+
+    private FingerprintRemovalClient mClient;
+    private int[] mBiometricIds = new int[]{1, 2};
+
+    @Before
+    public void setUp() {
+        when(mAidlSession.getSession()).thenReturn(mSession);
+
+        mClient = new FingerprintRemovalClient(mContext, () -> mAidlSession, mToken, mListener,
+                mBiometricIds, USER_ID, TAG, mBiometricUtils, SENSOR_ID,
+                mBiometricLogger, mBiometricContext, mAuthenticatorIds);
+    }
+
+    @Test
+    public void removalMultipleFingerprints() throws RemoteException {
+        when(mBiometricUtils.getBiometricsForUser(any(), anyInt())).thenReturn(
+                List.of(new Fingerprint("three", 3, 1)));
+        doAnswer(invocation -> {
+            mClient.onRemoved(new Fingerprint("one", 1, 1), 1);
+            mClient.onRemoved(new Fingerprint("two", 2, 1), 0);
+            return null;
+        }).when(mSession).removeEnrollments(mBiometricIds);
+        mClient.start(mCallback);
+
+        verify(mSession).removeEnrollments(mBiometricIds);
+        verify(mBiometricUtils, times(2)).removeBiometricForUser(eq(mContext),
+                eq(USER_ID), anyInt());
+        verifyNoMoreInteractions(mAuthenticatorIds);
+        verify(mListener, times(2)).onRemoved(any(), anyInt());
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void removeFingerprint_nullIdentifier() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onRemoved(null, 0);
+            return null;
+        }).when(mSession).removeEnrollments(mBiometricIds);
+        mClient.start(mCallback);
+
+        verify(mSession).removeEnrollments(mBiometricIds);
+        verify(mListener).onError(anyInt(), anyInt(), anyInt(), anyInt());
+        verify(mCallback).onClientFinished(mClient, false);
+    }
+
+    @Test
+    public void removeFingerprints_noFingerprintEnrolled() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onRemoved(new Fingerprint("one", 1, 1), 1);
+            mClient.onRemoved(new Fingerprint("two", 2, 1), 0);
+            return null;
+        }).when(mSession).removeEnrollments(mBiometricIds);
+        when(mBiometricUtils.getBiometricsForUser(any(), anyInt())).thenReturn(new ArrayList<>());
+
+        mClient.start(mCallback);
+
+        verify(mSession).removeEnrollments(mBiometricIds);
+        verify(mBiometricUtils, times(2)).removeBiometricForUser(eq(mContext),
+                eq(USER_ID), anyInt());
+        verify(mAuthenticatorIds).put(USER_ID, 0L);
+        verify(mListener, times(2)).onRemoved(any(), anyInt());
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java
new file mode 100644
index 0000000..a4746de
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java
@@ -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.server.biometrics.sensors.fingerprint.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+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.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+
+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 FingerprintResetLockoutClientTest {
+    private static final String TAG = "FingerprintResetLockoutClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private LockoutTracker mLockoutTracker;
+    @Mock
+    private LockoutResetDispatcher mLockoutResetDispatcher;
+    @Mock
+    private AuthSessionCoordinator mAuthSessionCoordinator;
+
+    private FingerprintResetLockoutClient mClient;
+
+    @Before
+    public void setUp() {
+        when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+        when(mAidlSession.getSession()).thenReturn(mSession);
+
+        mClient = new FingerprintResetLockoutClient(mContext, () -> mAidlSession, USER_ID, TAG,
+                SENSOR_ID, mBiometricLogger, mBiometricContext, new byte[69],
+                mLockoutTracker, mLockoutResetDispatcher,
+                BiometricManager.Authenticators.BIOMETRIC_STRONG);
+    }
+
+    @Test
+    public void resetLockout_onLockoutCleared() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onLockoutCleared();
+            return null;
+        }).when(mSession).resetLockout(any());
+        mClient.start(mCallback);
+
+        verify(mSession).resetLockout(any());
+        verify(mLockoutTracker).setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_NONE);
+        verify(mLockoutTracker).resetFailedAttemptsForUser(true, USER_ID);
+        verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(SENSOR_ID);
+        verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID),
+                eq(BiometricManager.Authenticators.BIOMETRIC_STRONG), anyLong());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java
new file mode 100644
index 0000000..f19b2f7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.aidl;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+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.ClientMonitorCallback;
+
+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 FingerprintRevokeChallengeClientTest {
+    private static final String TAG = "FingerprintRevokeChallengeClientTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+    private static final long CHALLENGE = 200;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private AidlSession mAidlSession;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private ClientMonitorCallback mCallback;
+    @Mock
+    private Context mContext;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private BiometricContext mBiometricContext;
+
+    private FingerprintRevokeChallengeClient mClient;
+
+    @Before
+    public void setUp() {
+        when(mAidlSession.getSession()).thenReturn(mSession);
+
+        mClient = new FingerprintRevokeChallengeClient(mContext, () -> mAidlSession, mToken,
+                USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, CHALLENGE);
+    }
+
+    @Test
+    public void revokeChallenge_sameChallenge() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onChallengeRevoked(CHALLENGE);
+            return null;
+        }).when(mSession).revokeChallenge(CHALLENGE);
+        mClient.start(mCallback);
+
+        verify(mSession).revokeChallenge(CHALLENGE);
+        verify(mCallback).onClientFinished(mClient, true);
+    }
+
+    @Test
+    public void revokeChallenge_differentChallenge() throws RemoteException {
+        doAnswer(invocation -> {
+            mClient.onChallengeRevoked(CHALLENGE + 1);
+            return null;
+        }).when(mSession).revokeChallenge(CHALLENGE);
+        mClient.start(mCallback);
+
+        verify(mSession).revokeChallenge(CHALLENGE);
+        verify(mCallback).onClientFinished(mClient, false);
+    }
+}
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 15d7601..4102600 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
@@ -75,7 +75,7 @@
     @Mock
     private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
     @Mock
-    private Sensor.HalSessionCallback.Callback mHalSessionCallback;
+    private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
     @Mock
     private LockoutResetDispatcher mLockoutResetDispatcher;
     @Mock
@@ -97,7 +97,7 @@
     private final LockoutCache mLockoutCache = new LockoutCache();
 
     private UserAwareBiometricScheduler mScheduler;
-    private Sensor.HalSessionCallback mHalCallback;
+    private AidlResponseHandler mHalCallback;
 
     @Before
     public void setUp() {
@@ -113,10 +113,9 @@
                 mBiometricService,
                 () -> USER_ID,
                 mUserSwitchCallback);
-        mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
-                TAG, mScheduler, SENSOR_ID,
-                USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
-                mHalSessionCallback);
+        mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
+                mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
+                mHardwareUnavailableCallback);
     }
 
     @Test
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/AidlToHidlAdapterTest.java
new file mode 100644
index 0000000..b78ba82
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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 org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.hardware.keymaster.Timestamp;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+
+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 AidlToHidlAdapterTest {
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private IBiometricsFingerprint mSession;
+    @Mock
+    private AidlResponseHandler mAidlResponseHandler;
+    @Mock
+    private HardwareAuthToken mHardwareAuthToken;
+
+    private final long mChallenge = 100L;
+    private final int mUserId = 0;
+    private AidlToHidlAdapter mAidlToHidlAdapter;
+
+    @Before
+    public void setUp() {
+        mAidlToHidlAdapter = new AidlToHidlAdapter(() -> mSession, mUserId,
+                mAidlResponseHandler);
+        mHardwareAuthToken.timestamp = new Timestamp();
+        mHardwareAuthToken.mac = new byte[10];
+    }
+
+    @Test
+    public void testGenerateChallenge() throws RemoteException {
+        when(mSession.preEnroll()).thenReturn(mChallenge);
+        mAidlToHidlAdapter.generateChallenge();
+
+        verify(mSession).preEnroll();
+        verify(mAidlResponseHandler).onChallengeGenerated(mChallenge);
+    }
+
+    @Test
+    public void testRevokeChallenge() throws RemoteException {
+        mAidlToHidlAdapter.revokeChallenge(mChallenge);
+
+        verify(mSession).postEnroll();
+        verify(mAidlResponseHandler).onChallengeRevoked(0L);
+    }
+
+    @Test
+    public void testEnroll() throws RemoteException {
+        final ICancellationSignal cancellationSignal =
+                mAidlToHidlAdapter.enroll(mHardwareAuthToken);
+
+        verify(mSession).enroll(any(), anyInt(), eq(AidlToHidlAdapter.ENROLL_TIMEOUT_SEC));
+
+        cancellationSignal.cancel();
+
+        verify(mSession).cancel();
+    }
+
+    @Test
+    public void testAuthenticate() throws RemoteException {
+        final int operationId = 2;
+        final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId);
+
+        verify(mSession).authenticate(operationId, mUserId);
+
+        cancellationSignal.cancel();
+
+        verify(mSession).cancel();
+    }
+
+    @Test
+    public void testDetectInteraction() throws RemoteException {
+        final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction();
+
+        verify(mSession).authenticate(0 /* operationId */, mUserId);
+
+        cancellationSignal.cancel();
+
+        verify(mSession).cancel();
+    }
+
+    @Test
+    public void testEnumerateEnrollments() throws RemoteException {
+        mAidlToHidlAdapter.enumerateEnrollments();
+
+        verify(mSession).enumerate();
+    }
+
+    @Test
+    public void testRemoveEnrollment() throws RemoteException {
+        final int[] enrollmentIds = new int[]{1};
+        mAidlToHidlAdapter.removeEnrollments(enrollmentIds);
+
+        verify(mSession).remove(mUserId, enrollmentIds[0]);
+    }
+
+    @Test
+    public void testRemoveMultipleEnrollments() throws RemoteException {
+        final int[] enrollmentIds = new int[]{1, 2};
+        mAidlToHidlAdapter.removeEnrollments(enrollmentIds);
+
+        verify(mSession).remove(mUserId, 0);
+    }
+
+    @Test
+    public void testResetLockout() throws RemoteException {
+        mAidlToHidlAdapter.resetLockout(mHardwareAuthToken);
+
+        verify(mAidlResponseHandler).onLockoutCleared();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index e7777f7..12d6161 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.contentcapture;
 
+import static android.view.contentprotection.flags.Flags.FLAG_PARSE_GROUPS_CONFIG_ENABLED;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -33,6 +35,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.contentcapture.ContentCaptureServiceInfo;
 import android.view.contentcapture.ContentCaptureEvent;
 
@@ -56,6 +59,8 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.List;
+
 /**
  * Test for {@link ContentCaptureManagerService}.
  *
@@ -84,6 +89,8 @@
 
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock private UserManagerInternal mMockUserManagerInternal;
 
     @Mock private ContentProtectionBlocklistManager mMockContentProtectionBlocklistManager;
@@ -436,6 +443,82 @@
         verify(mMockRemoteContentProtectionService).onLoginDetected(PARCELED_EVENTS);
     }
 
+    @Test
+    public void parseContentProtectionGroupsConfig_disabled_null() {
+        mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig(null)).isEmpty();
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_disabled_empty() {
+        mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("")).isEmpty();
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_disabled_notEmpty() {
+        mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("a")).isEmpty();
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_enabled_null() {
+        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig(null)).isEmpty();
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_enabled_empty() {
+        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("")).isEmpty();
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_enabled_singleValue() {
+        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("a"))
+                .isEqualTo(List.of(List.of("a")));
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_enabled_multipleValues() {
+        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("a,b"))
+                .isEqualTo(List.of(List.of("a", "b")));
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_enabled_multipleGroups() {
+        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("a,b;c;d,e"))
+                .isEqualTo(List.of(List.of("a", "b"), List.of("c"), List.of("d", "e")));
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_enabled_emptyValues() {
+        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("a;b;;;c;,d,,e,;,;"))
+                .isEqualTo(List.of(List.of("a"), List.of("b"), List.of("c"), List.of("d", "e")));
+    }
+
     private class TestContentCaptureManagerService extends ContentCaptureManagerService {
 
         TestContentCaptureManagerService() {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 9b32a80..de3cfbf 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -54,8 +54,8 @@
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 import com.android.server.pm.PackageManagerLocal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 16fdfb1..76aa40c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -77,7 +77,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.AlarmManagerInternal;
-import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.pm.PackageManagerLocal;
 import com.android.server.pm.UserManagerInternal;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
index a029db9..fa3c7a4c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -22,7 +22,7 @@
 
 import android.content.Context;
 
-import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 23f14f8..02b86db 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -53,8 +53,8 @@
 
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index d85768d..f94aff7 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -26,6 +26,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -66,6 +67,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 import com.android.server.testutils.OffsettableClock;
 import com.android.server.wm.WindowManagerInternal;
@@ -128,6 +130,14 @@
                 }
             };
 
+    private final MediaProjectionManagerService.Injector mMediaProjectionMetricsLoggerInjector =
+            new MediaProjectionManagerService.Injector() {
+                @Override
+                MediaProjectionMetricsLogger mediaProjectionMetricsLogger() {
+                    return mMediaProjectionMetricsLogger;
+                }
+            };
+
     private Context mContext;
     private MediaProjectionManagerService mService;
     private OffsettableClock mClock;
@@ -142,6 +152,8 @@
     private PackageManager mPackageManager;
     @Mock
     private IMediaProjectionWatcherCallback mWatcherCallback;
+    @Mock
+    private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
     @Captor
     private ArgumentCaptor<ContentRecordingSession> mSessionCaptor;
 
@@ -734,6 +746,25 @@
     }
 
     @Test
+    public void setContentRecordingSession_success_logsCaptureInProgress()
+            throws Exception {
+        mService.addCallback(mWatcherCallback);
+        MediaProjectionManagerService service = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+
+        service.setContentRecordingSession(DISPLAY_SESSION);
+
+        verify(mMediaProjectionMetricsLogger).notifyProjectionStateChange(
+                projection.uid,
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS,
+                FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN
+        );
+    }
+
+    @Test
     public void setContentRecordingSession_notifiesListenersOnCallbackLooper()
             throws Exception {
         mService = new MediaProjectionManagerService(mContext, mTestLooperInjector);
diff --git a/services/tests/servicestests/src/com/android/server/pdb/OWNERS b/services/tests/servicestests/src/com/android/server/pdb/OWNERS
new file mode 100644
index 0000000..6dfb888
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pdb/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/pdb/OWNERS
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
index ca5cfa5..9544106 100644
--- a/services/tests/vibrator/Android.bp
+++ b/services/tests/vibrator/Android.bp
@@ -27,6 +27,7 @@
         "androidx.test.runner",
         "androidx.test.rules",
         "androidx.test.ext.junit",
+        "flag-junit",
         "frameworks-base-testutils",
         "frameworks-services-vibrator-testutils",
         "junit",
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
index bc826a3..04158c4 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
@@ -31,6 +31,8 @@
 import android.content.res.Resources;
 import android.os.VibrationEffect;
 import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.AtomicFile;
 import android.util.SparseArray;
 
@@ -49,6 +51,8 @@
 import java.io.FileOutputStream;
 
 public class HapticFeedbackCustomizationTest {
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Rule public MockitoRule rule = MockitoJUnit.rule();
 
     // Pairs of valid vibration XML along with their equivalent VibrationEffect.
@@ -77,6 +81,7 @@
     @Before
     public void setUp() {
         when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true);
+        mSetFlagsRule.enableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
     }
 
     @Test
@@ -87,6 +92,21 @@
     }
 
     @Test
+    public void testParseCustomizations_featureFlagDisabled_returnsNull() throws Exception {
+        mSetFlagsRule.disableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
+        // Valid customization XML.
+        String xml = "<haptic-feedback-constants>"
+                + "<constant id=\"10\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>"
+                + "</haptic-feedback-constants>";
+        setupCustomizationFile(xml);
+
+        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
+                .isNull();
+    }
+
+    @Test
     public void testParseCustomizations_oneVibrationCustomization_success() throws Exception {
         String xml = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
index 6c48a69..9f43a17 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
@@ -94,6 +94,16 @@
     }
 
     @Test
+    public void test_selectiveCloneLunchRemoteTransition() {
+        final RemoteTransition transition = mock(RemoteTransition.class);
+        final SafeActivityOptions clone = new SafeActivityOptions(
+                ActivityOptions.makeRemoteTransition(transition))
+                .selectiveCloneLaunchOptions();
+
+        assertSame(clone.getOriginalOptions().getRemoteTransition(), transition);
+    }
+
+    @Test
     public void test_getOptions() {
         // Mock everything necessary
         MockitoSession mockingSession = mockitoSession()
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 5d2f27d..35e2fcf 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -37,8 +37,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.hardware.usb.IUsbManager;
 import android.hardware.usb.IDisplayPortAltModeInfoListener;
+import android.hardware.usb.IUsbManager;
 import android.hardware.usb.IUsbOperationInternal;
 import android.hardware.usb.ParcelableUsbPort;
 import android.hardware.usb.UsbAccessory;
@@ -46,7 +46,6 @@
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
-import android.hardware.usb.DisplayPortAltModeInfo;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
@@ -1215,6 +1214,20 @@
                     mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, "  ")),
                             "", 0);
                 }
+            } else if ("enable-usb-data".equals(args[0]) && args.length == 3) {
+                final String portId = args[1];
+                final boolean enable = Boolean.parseBoolean(args[2]);
+
+                if (mPortManager != null) {
+                    for (UsbPort p : mPortManager.getPorts()) {
+                        if (p.getId().equals(portId)) {
+                            int res = p.enableUsbData(enable);
+                            Slog.i(TAG, "enableUsbData " + portId + " status " + res);
+                            break;
+                        }
+                    }
+                }
+
             } else if ("ports".equals(args[0]) && args.length == 1) {
                 if (mPortManager != null) {
                     mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, "  ")),
@@ -1293,6 +1306,11 @@
                 pw.println("reset-displayport-status can also be used in order to set");
                 pw.println("the DisplayPortInfo to default values.");
                 pw.println();
+                pw.println("Example enableUsbData");
+                pw.println("This dumpsys command functions for both simulated and real ports.");
+                pw.println("  dumpsys usb enable-usb-data \"matrix\" true");
+                pw.println("  dumpsys usb enable-usb-data \"matrix\" false");
+                pw.println();
                 pw.println("Example USB device descriptors:");
                 pw.println("  dumpsys usb dump-descriptors -dump-short");
                 pw.println("  dumpsys usb dump-descriptors -dump-tree");
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 63e91ad..7a0bf90 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -10523,6 +10523,8 @@
         auto_data_switch_rat_signal_score_string_bundle.putIntArray(
                 "LTE", new int[]{3731, 5965, 8618, 11179, 13384});
         auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "LTE_CA", new int[]{3831, 6065, 8718, 11379, 13484});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
                 "NR_SA", new int[]{5288, 6795, 6955, 7562, 9713});
         auto_data_switch_rat_signal_score_string_bundle.putIntArray(
                 "NR_NSA", new int[]{5463, 6827, 8029, 9007, 9428});
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
index 79348d1..ae7c2a9 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
@@ -247,7 +247,7 @@
 
             int rc = 0;
             try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
-                transaction.setFrameRateCategory(mSurfaceControl, category);
+                transaction.setFrameRateCategory(mSurfaceControl, category, false);
                 transaction.apply();
             }
             return rc;
diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml
index 63acddf..0f47980 100644
--- a/tests/FlickerTests/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AndroidTestTemplate.xml
@@ -61,9 +61,7 @@
         <option name="shell-timeout" value="6600s"/>
         <option name="test-timeout" value="6600s"/>
         <option name="hidden-api-checks" value="false"/>
-        <!-- TODO(b/288396763): re-enable when PerfettoListener is fixed
         <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
-        -->
         <!-- PerfettoListener related arguments -->
         <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
         <option name="instrumentation-arg"
diff --git a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
index 56ab755..7e43566 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
@@ -37,6 +37,8 @@
     private var window: Window? = null
     private var currentModeDisplay: TextView? = null
 
+    private var desiredRatio = 0.0f
+
     override fun onFinishInflate() {
         super.onFinishInflate()
         val window = window ?: throw IllegalStateException("Failed to attach window")
@@ -67,6 +69,7 @@
 
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
+        desiredRatio = window?.desiredHdrHeadroom ?: 0.0f
         val hdrVis = if (display.isHdrSdrRatioAvailable) {
             display.registerHdrSdrRatioChangedListener({ it.run() }, hdrSdrListener)
             View.VISIBLE
@@ -83,6 +86,11 @@
     }
 
     private fun setColorMode(newMode: Int) {
+        if (newMode == ActivityInfo.COLOR_MODE_HDR &&
+                window!!.colorMode == ActivityInfo.COLOR_MODE_HDR) {
+            desiredRatio = (desiredRatio + 1) % 5.0f
+            window!!.desiredHdrHeadroom = desiredRatio
+        }
         window!!.colorMode = newMode
         updateModeInfoDisplay()
     }