Merge "Flag AudioPolicy.updateMixingRules API" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 31d2764..36430b6 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}",
@@ -36,14 +37,18 @@
     ":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}",
     ":android.app.flags-aconfig-java{.generated_srcjars}",
     ":android.credentials.flags-aconfig-java{.generated_srcjars}",
     ":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 {
@@ -244,6 +249,11 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+cc_aconfig_library {
+    name: "aconfig_view_flags_c_lib",
+    aconfig_declarations: "android.view.flags-aconfig",
+}
+
 // View.accessibility
 aconfig_declarations {
     name: "android.view.accessibility.flags-aconfig",
@@ -288,6 +298,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "android.content.pm.flags-aconfig-java-host",
+    aconfig_declarations: "android.content.pm.flags-aconfig",
+    host_supported: true,
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Media BetterTogether
 aconfig_declarations {
     name: "com.android.media.flags.bettertogether-aconfig",
@@ -319,6 +336,24 @@
     name: "android.permission.flags-aconfig-java",
     aconfig_declarations: "android.permission.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
+    min_sdk_version: "30",
+    apex_available: [
+        "com.android.permission",
+    ],
+
+}
+
+// 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
@@ -341,6 +376,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",
@@ -348,6 +389,12 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "com.android.internal.foldables.flags-aconfig-java",
+    aconfig_declarations: "fold_lock_setting_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Multi user
 aconfig_declarations {
     name: "android.multiuser.flags-aconfig",
@@ -441,3 +488,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/Android.bp b/Android.bp
index b1b332a..a507465a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -64,6 +64,7 @@
     srcs: [
         // Java/AIDL sources under frameworks/base
         ":framework-annotations",
+        ":ravenwood-annotations",
         ":framework-blobstore-sources",
         ":framework-core-sources",
         ":framework-drm-sources",
@@ -284,6 +285,7 @@
         enforce_permissions_exceptions: [
             // Do not add entries to this list.
             ":framework-annotations",
+            ":ravenwood-annotations",
             ":framework-blobstore-sources",
             ":framework-core-sources",
             ":framework-drm-sources",
@@ -409,7 +411,6 @@
         "audiopolicy-aidl-java",
         "sounddose-aidl-java",
         "modules-utils-expresslog",
-        "hoststubgen-annotations",
     ],
 }
 
@@ -838,4 +839,5 @@
     "AconfigFlags.bp",
     "ProtoLibraries.bp",
     "TestProtoLibraries.bp",
+    "Ravenwood.bp",
 ]
diff --git a/Android.mk b/Android.mk
index d9e202c..e2c1ed8 100644
--- a/Android.mk
+++ b/Android.mk
@@ -69,9 +69,6 @@
 .PHONY: framework-doc-stubs
 framework-doc-stubs: $(SDK_METADATA)
 
-# Run this for checkbuild
-checkbuild: doc-comment-check-docs
-
 # Include subdirectory makefiles
 # ============================================================
 
diff --git a/OWNERS b/OWNERS
index 4e5c7d8..023bdef 100644
--- a/OWNERS
+++ b/OWNERS
@@ -34,3 +34,6 @@
 
 per-file ZYGOTE_OWNERS = file:/ZYGOTE_OWNERS
 per-file SQLITE_OWNERS = file:/SQLITE_OWNERS
+
+per-file *ravenwood* = file:ravenwood/OWNERS
+per-file *Ravenwood* = file:ravenwood/OWNERS
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/Ravenwood.bp b/Ravenwood.bp
new file mode 100644
index 0000000..9218cc9
--- /dev/null
+++ b/Ravenwood.bp
@@ -0,0 +1,70 @@
+// 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.
+
+// We need this "trampoline" rule to force soong to give a host-side jar to
+// framework-minus-apex.ravenwood. Otherwise, soong would mix up the arch (?) and we'd get
+// a dex jar.
+java_library {
+    name: "framework-minus-apex-for-hoststubgen",
+    installable: false, // host only jar.
+    static_libs: [
+        "framework-minus-apex",
+    ],
+    sdk_version: "core_platform",
+    visibility: ["//visibility:private"],
+}
+
+// Generate the stub/impl from framework-all, with hidden APIs.
+java_genrule_host {
+    name: "framework-minus-apex.ravenwood-base",
+    tools: ["hoststubgen"],
+    cmd: "$(location hoststubgen) " +
+        "@$(location :ravenwood-standard-options) " +
+
+        "--out-stub-jar $(location ravenwood_stub.jar) " +
+        "--out-impl-jar $(location ravenwood.jar) " +
+
+        "--gen-keep-all-file $(location hoststubgen_keep_all.txt) " +
+        "--gen-input-dump-file $(location hoststubgen_dump.txt) " +
+
+        "--in-jar $(location :framework-minus-apex-for-hoststubgen) " +
+        "--policy-override-file $(location framework-minus-apex-ravenwood-policies.txt) ",
+    srcs: [
+        ":framework-minus-apex-for-hoststubgen",
+        "framework-minus-apex-ravenwood-policies.txt",
+        ":ravenwood-standard-options",
+    ],
+    out: [
+        "ravenwood.jar",
+        "ravenwood_stub.jar", // It's not used. TODO: Update hoststubgen to make it optional.
+
+        // Following files are created just as FYI.
+        "hoststubgen_keep_all.txt",
+        "hoststubgen_dump.txt",
+    ],
+    visibility: ["//visibility:private"],
+}
+
+// Extract the impl jar from "framework-minus-apex.ravenwood-base" for subsequent build rules.
+java_genrule_host {
+    name: "framework-minus-apex.ravenwood",
+    cmd: "cp $(in) $(out)",
+    srcs: [
+        ":framework-minus-apex.ravenwood-base{ravenwood.jar}",
+    ],
+    out: [
+        "framework-minus-apex.ravenwood.jar",
+    ],
+    visibility: ["//visibility:public"],
+}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index f1403bd5..e833bb9 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -439,7 +439,7 @@
      * provides an easy way to tell whether the job is being executed due to the deadline
      * expiring. Note: If the job is running because its deadline expired, it implies that its
      * constraints will not be met. However,
-     * {@link android.app.job.JobInfo.Builder#setPeriodic(boolean) periodic jobs} will only ever
+     * {@link android.app.job.JobInfo.Builder#setPeriodic(long) periodic jobs} will only ever
      * run when their constraints are satisfied, therefore, the constraints will still be satisfied
      * for a periodic job even if the deadline has expired.
      */
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/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 43d2ae9..f47766e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -1335,13 +1335,13 @@
     private void handleOpTimeoutLocked() {
         switch (mVerb) {
             case VERB_BINDING:
-                onSlowAppResponseLocked(/* reschedule */ false, /* updateStopReasons */ true,
+                // The system may have been too busy. Don't drop the job or trigger an ANR.
+                onSlowAppResponseLocked(/* reschedule */ true, /* updateStopReasons */ true,
                         /* texCounterMetricId */
                         "job_scheduler.value_cntr_w_uid_slow_app_response_binding",
                         /* debugReason */ "timed out while binding",
                         /* anrMessage */ "Timed out while trying to bind",
-                        CompatChanges.isChangeEnabled(ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES,
-                            mRunningJob.getUid()));
+                        /* triggerAnr */ false);
                 break;
             case VERB_STARTING:
                 // Client unresponsive - wedged or failed to respond in time. We don't really
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
index 2b7438c..fdeb072 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
@@ -536,10 +536,13 @@
         static final String KEY_LAUNCH_TIME_ALLOWANCE_MS =
                 PC_CONSTANT_PREFIX + "launch_time_allowance_ms";
 
-        private static final long DEFAULT_LAUNCH_TIME_THRESHOLD_MS = 7 * HOUR_IN_MILLIS;
-        private static final long DEFAULT_LAUNCH_TIME_ALLOWANCE_MS = 20 * MINUTE_IN_MILLIS;
+        private static final long DEFAULT_LAUNCH_TIME_THRESHOLD_MS = HOUR_IN_MILLIS;
+        private static final long DEFAULT_LAUNCH_TIME_ALLOWANCE_MS = 30 * MINUTE_IN_MILLIS;
 
-        /** How much time each app will have to run jobs within their standby bucket window. */
+        /**
+         * The earliest amount of time before the next estimated app launch time that we may choose
+         * to run a prefetch job for the app.
+         */
         public long LAUNCH_TIME_THRESHOLD_MS = DEFAULT_LAUNCH_TIME_THRESHOLD_MS;
 
         /**
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index 8d8fc12..30b4423 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -20,41 +20,6 @@
 // The API doc generation is done by the various droiddoc modules each of which
 // is for different format.
 
-/////////////////////////////////////////////////////////////////////
-// stub source files are generated using metalava
-/////////////////////////////////////////////////////////////////////
-
-framework_docs_only_libs = [
-    "voip-common",
-    "android.test.mock",
-    "android-support-annotations",
-    "android-support-compat",
-    "android-support-core-ui",
-    "android-support-core-utils",
-    "android-support-design",
-    "android-support-dynamic-animation",
-    "android-support-exifinterface",
-    "android-support-fragment",
-    "android-support-media-compat",
-    "android-support-percent",
-    "android-support-transition",
-    "android-support-v7-cardview",
-    "android-support-v7-gridlayout",
-    "android-support-v7-mediarouter",
-    "android-support-v7-palette",
-    "android-support-v7-preference",
-    "android-support-v13",
-    "android-support-v14-preference",
-    "android-support-v17-leanback",
-    "android-support-vectordrawable",
-    "android-support-animatedvectordrawable",
-    "android-support-v7-appcompat",
-    "android-support-v7-recyclerview",
-    "android-support-v8-renderscript",
-    "android-support-multidex",
-    "android-support-multidex-instrumentation",
-]
-
 // These defaults enable doc-stub generation, api lint database generation and sdk value generation.
 stubs_defaults {
     name: "android-non-updatable-doc-stubs-defaults",
@@ -65,7 +30,6 @@
         ":android-test-mock-sources",
         ":android-test-runner-sources",
     ],
-    libs: framework_docs_only_libs,
     create_doc_stubs: true,
     write_sdk_values: true,
 }
@@ -197,7 +161,7 @@
     name: "framework-docs-default",
     sdk_version: "none",
     system_modules: "none",
-    libs: framework_docs_only_libs + [
+    libs: [
         "stub-annotations",
         "unsupportedappusage",
     ],
@@ -236,20 +200,6 @@
     },
 }
 
-doc_defaults {
-    name: "framework-dokka-docs-default",
-}
-
-droiddoc {
-    name: "doc-comment-check-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-stubs",
-    ],
-    args: framework_docs_only_args + " -referenceonly -parsecomments",
-    installable: false,
-}
-
 droiddoc {
     name: "offline-sdk-docs",
     defaults: ["framework-docs-default"],
@@ -303,70 +253,6 @@
 }
 
 droiddoc {
-    name: "online-sdk-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-stubs",
-    ],
-    hdf: [
-        "android.whichdoc online",
-        "android.hasSamples true",
-    ],
-    proofread_file: "online-sdk-docs-proofread.txt",
-    args: framework_docs_only_args +
-        " -toroot / -samplegroup Admin " +
-        " -samplegroup Background " +
-        " -samplegroup Connectivity " +
-        " -samplegroup Content " +
-        " -samplegroup Input " +
-        " -samplegroup Media " +
-        " -samplegroup Notification " +
-        " -samplegroup RenderScript " +
-        " -samplegroup Security " +
-        " -samplegroup Sensors " +
-        " -samplegroup System " +
-        " -samplegroup Testing " +
-        " -samplegroup UI " +
-        " -samplegroup Views " +
-        " -samplegroup Wearable -samplesdir development/samples/browseable ",
-}
-
-droiddoc {
-    name: "online-system-api-sdk-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-system-stubs",
-    ],
-    hdf: [
-        "android.whichdoc online",
-        "android.hasSamples true",
-    ],
-    proofread_file: "online-system-api-sdk-docs-proofread.txt",
-    args: framework_docs_only_args +
-        " -referenceonly " +
-        " -title \"Android SDK - Including system APIs.\" " +
-        " -hide 101 " +
-        " -hide 104 " +
-        " -hide 108 " +
-        " -toroot / -samplegroup Admin " +
-        " -samplegroup Background " +
-        " -samplegroup Connectivity " +
-        " -samplegroup Content " +
-        " -samplegroup Input " +
-        " -samplegroup Media " +
-        " -samplegroup Notification " +
-        " -samplegroup RenderScript " +
-        " -samplegroup Security " +
-        " -samplegroup Sensors " +
-        " -samplegroup System " +
-        " -samplegroup Testing " +
-        " -samplegroup UI " +
-        " -samplegroup Views " +
-        " -samplegroup Wearable -samplesdir development/samples/browseable ",
-    installable: false,
-}
-
-droiddoc {
     name: "ds-docs-java",
     defaults: ["framework-docs-default"],
     srcs: [
@@ -397,7 +283,6 @@
 
 droiddoc {
     name: "ds-docs-kt",
-    defaults: ["framework-dokka-docs-default"],
     srcs: [
         ":framework-doc-stubs",
     ],
@@ -476,44 +361,3 @@
         " -atLinksNavtree " +
         " -navtreeonly ",
 }
-
-droiddoc {
-    name: "online-sdk-dev-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-stubs",
-    ],
-    hdf: [
-        "android.whichdoc online",
-        "android.hasSamples true",
-    ],
-    proofread_file: "online-sdk-dev-docs-proofread.txt",
-    args: framework_docs_only_args +
-        " -toroot / -samplegroup Admin " +
-        " -samplegroup Background " +
-        " -samplegroup Connectivity " +
-        " -samplegroup Content " +
-        " -samplegroup Input " +
-        " -samplegroup Media " +
-        " -samplegroup Notification " +
-        " -samplegroup RenderScript " +
-        " -samplegroup Security " +
-        " -samplegroup Sensors " +
-        " -samplegroup System " +
-        " -samplegroup Testing " +
-        " -samplegroup UI " +
-        " -samplegroup Views " +
-        " -samplegroup Wearable -samplesdir development/samples/browseable ",
-}
-
-droiddoc {
-    name: "hidden-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-stubs",
-    ],
-    proofread_file: "hidden-docs-proofread.txt",
-    args: framework_docs_only_args +
-        " -referenceonly " +
-        " -title \"Android SDK - Including hidden APIs.\"",
-}
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 762d9d1..d9e72b8 100644
--- a/api/javadoc-lint-baseline
+++ b/api/javadoc-lint-baseline
@@ -8,9 +8,12 @@
 android/adservices/ondevicepersonalization/ExecuteOutput.java:31: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteOutput [101]
 android/adservices/ondevicepersonalization/ExecuteOutput.java:93: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteOutput.Builder [101]
 android/adservices/ondevicepersonalization/IsolatedService.java:18: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.IsolatedService [101]
+android/adservices/ondevicepersonalization/IsolatedService.java:18: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#execute" in android.adservices.ondevicepersonalization.IsolatedService [101]
 android/adservices/ondevicepersonalization/IsolatedService.java:119: lint: Unresolved link/see tag "IsolatedCmputationCallback#onWebViewEvent()" in android.adservices.ondevicepersonalization.IsolatedService [101]
+android/adservices/ondevicepersonalization/IsolatedService.java:119: lint: Unresolved link/see tag "IsolatedCmputationCallback#onEvent()" in android.adservices.ondevicepersonalization.IsolatedService [101]
 android/adservices/ondevicepersonalization/IsolatedService.java:119: lint: Unresolved link/see tag "WebView" in android.adservices.ondevicepersonalization.IsolatedService [101]
 android/adservices/ondevicepersonalization/IsolatedWorker.java:9: lint: Unresolved link/see tag "RunTimeException" in android.adservices.ondevicepersonalization.IsolatedWorker [101]
+android/adservices/ondevicepersonalization/IsolatedWorker.java:24: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#execute" in android.adservices.ondevicepersonalization.IsolatedWorker [101]
 android/adservices/ondevicepersonalization/IsolatedWorker.java:57: lint: Unresolved link/see tag "#onExecute()" in android.adservices.ondevicepersonalization.IsolatedWorker [101]
 android/adservices/ondevicepersonalization/IsolatedWorker.java:74: lint: Unresolved link/see tag "#onRender()" in android.adservices.ondevicepersonalization.IsolatedWorker [101]
 android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:-11: lint: Unresolved link/see tag "requestSurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
@@ -50,16 +53,6 @@
 android/adservices/ondevicepersonalization/WebViewEventInput.java:30: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.WebViewEventInput [101]
 android/adservices/ondevicepersonalization/WebViewEventInput.java:41: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.EventUrlProvider#createEventTrackingUrlWithResponse() EventUrlProvider#createEventTrackingUrlWithResponse()" in android.adservices.ondevicepersonalization.WebViewEventInput [101]
 android/adservices/ondevicepersonalization/WebViewEventOutput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onWebViewEvent() IsolatedWorker#onWebViewEvent()" in android.adservices.ondevicepersonalization.WebViewEventOutput [101]
-android/app/ActivityOptions.java:366: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101]
-android/app/ActivityOptions.java:370: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101]
-android/app/ActivityOptions.java:384: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101]
-android/app/ApplicationStartInfo.java:96: lint: Unresolved link/see tag "#START_TIMESTAMP_JAVA_CLASSLOADING_COMPLETE" in android.app.ApplicationStartInfo [101]
-android/app/BroadcastOptions.java:132: lint: Unresolved link/see tag "#setDeliveryGroupMatchingFilter(android.content.IntentFilter)" in android.app.BroadcastOptions [101]
-android/app/GrammaticalInflectionManager.java:60: lint: Unresolved link/see tag "android.os.Environment#getDataSystemCeDirectory(int)" in android.app.GrammaticalInflectionManager [101]
-android/app/Notification.java:509: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.app.Notification [101]
-android/app/Notification.java:650: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.app.Notification [101]
-android/app/Notification.java:1866: lint: Unresolved link/see tag "/*missing*/" in android.app.Notification.Action [101]
-android/app/Notification.java:4796: lint: Unresolved link/see tag "android.content.pm.ShortcutInfo#setLongLived() ShortcutInfo#setLongLived()" in android.app.Notification.MessagingStyle [101]
 android/app/admin/DevicePolicyManager.java:2670: lint: Unresolved link/see tag "android.os.UserManager#DISALLOW_CAMERA UserManager#DISALLOW_CAMERA" in android.app.admin.DevicePolicyManager [101]
 android/app/admin/DevicePolicyManager.java:7257: lint: Unresolved link/see tag "android.app.admin.DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY" in android.app.admin.DevicePolicyManager [101]
 android/app/admin/DevicePolicyManager.java:7425: lint: Unresolved link/see tag "ACTION_DEVICE_FINANCING_STATE_CHANGED" in android.app.admin.DevicePolicyManager [101]
@@ -80,40 +73,6 @@
 android/app/appsearch/SearchSpec.java:913: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.SearchSpec.Builder [101]
 android/app/appsearch/SearchSpec.java:925: lint: Unresolved link/see tag "Features#VERBATIM_SEARCH" in android.app.appsearch.SearchSpec.Builder [101]
 android/app/appsearch/SearchSpec.java:929: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.SearchSpec.Builder [101]
-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/content/om/OverlayIdentifier.java:20: lint: Unresolved link/see tag "android.content.om.OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)" in android.content.om.OverlayIdentifier [101]
-android/content/om/OverlayInfo.java:78: lint: Unresolved link/see tag "android.content.om.OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)" in android.content.om.OverlayInfo [101]
-android/content/om/OverlayManager.java:9: lint: Unresolved link/see tag "android.content.om.OverlayManagerTransaction#commit()" in android.content.om.OverlayManager [101]
-android/content/pm/PackageInstaller.java:2232: lint: Unresolved link/see tag "android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS INSTALL_GRANT_RUNTIME_PERMISSIONS" in android.content.pm.PackageInstaller.SessionParams [101]
-android/content/pm/ServiceInfo.java:176: lint: Unresolved link/see tag "android.app.job.JobInfo.Builder#setDataTransfer" in android.content.pm.ServiceInfo [101]
-android/content/pm/verify/domain/DomainVerificationUserState.java:82: lint: Unresolved link/see tag "android.content.pm.verify.domain.DomainVerificationUserState.DomainState DomainState" in android.content.pm.verify.domain.DomainVerificationUserState [101]
-android/content/res/Resources.java:958: lint: Unresolved link/see tag "android.annotation.UiContext" in android.content.res.Resources [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]
 android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101]
 android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations tables" in android.hardware.camera2.CameraCharacteristics [101]
@@ -139,11 +98,6 @@
 android/hardware/camera2/CaptureRequest.java:1501: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureRequest [101]
 android/hardware/camera2/CaptureResult.java:923: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureResult [101]
 android/hardware/camera2/CaptureResult.java:2337: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureResult [101]
-android/hardware/input/InputManager.java:215: lint: Unresolved link/see tag "android.hardware.input.InputManagerGlobal#getInputDevice InputManagerGlobal#getInputDevice" in android.hardware.input.InputManager.InputDeviceListener [101]
-android/inputmethodservice/AbstractInputMethodService.java:155: lint: Unresolved link/see tag "android.app.ActivityThread ActivityThread" in android.inputmethodservice.AbstractInputMethodService [101]
-android/inputmethodservice/InputMethodService.java:1078: lint: Unresolved link/see tag "android.widget.Editor" in android.inputmethodservice.InputMethodService [101]
-android/location/GnssSignalType.java:14: lint: Unresolved link/see tag "android.location.GnssStatus.ConstellationType GnssStatus.ConstellationType" in android.location.GnssSignalType [101]
-android/location/GnssSignalType.java:48: lint: Unresolved link/see tag "android.location.GnssStatus.ConstellationType GnssStatus.ConstellationType" in android.location.GnssSignalType [101]
 android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ALARM AttributeSdkUsage#USAGE_ALARM" in android.media.AudioAttributes.Builder [101]
 android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY" in android.media.AudioAttributes.Builder [101]
 android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE" in android.media.AudioAttributes.Builder [101]
@@ -162,8 +116,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]
@@ -199,100 +151,8 @@
 android/net/wifi/aware/SubscribeConfig.java:51: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.SubscribeConfig [101]
 android/net/wifi/aware/SubscribeConfig.java:276: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.SubscribeConfig.Builder [101]
 android/net/wifi/aware/SubscribeConfig.java:276: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.SubscribeConfig.Builder [101]
-android/os/BugreportManager.java:146: lint: Unresolved link/see tag "android.os.BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT" in android.os.BugreportManager.BugreportCallback [101]
-android/os/PowerManager.java:796: lint: Unresolved link/see tag "android.os.Temperature" in android.os.PowerManager.OnThermalStatusChangedListener [101]
-android/os/RemoteException.java:49: lint: Unresolved link/see tag "android.os.DeadSystemRuntimeException DeadSystemRuntimeException" in android.os.RemoteException [101]
-android/provider/Settings.java:374: lint: Unresolved link/see tag "android.credentials.CredentialManager#isEnabledCredentialProviderService()" in android.provider.Settings [101]
-android/provider/Settings.java:908: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService" in android.provider.Settings [101]
-android/provider/Settings.java:2181: lint: Unresolved link/see tag "android.app.time.TimeManager" in android.provider.Settings.Global [101]
-android/provider/Settings.java:2195: lint: Unresolved link/see tag "android.app.time.TimeManager" in android.provider.Settings.Global [101]
-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]
-android/service/notification/NotificationListenerService.java:1166: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService NotificationAssistantService" in android.service.notification.NotificationListenerService.Ranking [101]
-android/service/quickaccesswallet/WalletCard.java:285: lint: Unresolved link/see tag "PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS" in android.service.quickaccesswallet.WalletCard.Builder [101]
-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/telecom/Call.java:94: lint: unable to parse link/see tag: #playDtmfTone(char [101]
-android/telecom/CallControl.java:163: lint: Unresolved link/see tag "android.telecom.CallStreamingService CallStreamingService" in android.telecom.CallControl [101]
-android/telecom/CallControlCallback.java:63: lint: Unresolved link/see tag "android.telecom.CallAttributes.CallType" in android.telecom.CallControlCallback [101]
-android/telecom/PhoneAccountSuggestion.java:17: lint: Unresolved link/see tag "android.telecom.PhoneAccountSuggestionService PhoneAccountSuggestionService" in android.telecom.PhoneAccountSuggestion [101]
-android/telephony/CarrierConfigManager.java:2934: lint: Unresolved link/see tag "android.telephony.TelephonyManager.PremiumCapability TelephonyManager.PremiumCapability" in android.telephony.CarrierConfigManager [101]
-android/telephony/CarrierConfigManager.java:4020: lint: Unresolved link/see tag "ImsRegistrationImplBase.ImsRegistrationTech" in android.telephony.CarrierConfigManager.Ims [101]
-android/telephony/CarrierConfigManager.java:4108: lint: Unresolved link/see tag "ImsRegistrationImplBase.ImsRegistrationTech" in android.telephony.CarrierConfigManager.Ims [101]
-android/telephony/CarrierConfigManager.java:3920: lint: Unresolved link/see tag "android.telephony.ims.SipDelegateManager" in android.telephony.CarrierConfigManager.Ims [101]
-android/telephony/CarrierConfigManager.java:4001: lint: Unresolved link/see tag "ImsRegistrationImplBase.ImsRegistrationTech" in android.telephony.CarrierConfigManager.Ims [101]
-android/telephony/CarrierConfigManager.java:4089: lint: Unresolved link/see tag "ImsRegistrationImplBase.ImsRegistrationTech" in android.telephony.CarrierConfigManager.Ims [101]
-android/telephony/NetworkRegistrationInfo.java:131: lint: Unresolved link/see tag "android.telephony.Annotation.NetworkType NetworkType" in android.telephony.NetworkRegistrationInfo [101]
-android/telephony/PhoneStateListener.java:293: lint: Unresolved link/see tag "android.telephony.PreciseDisconnectCause PreciseDisconnectCause" in android.telephony.PhoneStateListener [101]
-android/telephony/PhoneStateListener.java:544: lint: Unresolved link/see tag "android.telephony.PreciseDisconnectCause PreciseDisconnectCause" in android.telephony.PhoneStateListener [101]
-android/telephony/SubscriptionManager.java:1385: lint: Unresolved link/see tag "Build.VERSION_CODES.P" in android.telephony.SubscriptionManager.OnSubscriptionsChangedListener [101]
-android/telephony/SubscriptionManager.java:1385: lint: Unresolved link/see tag "Build.VERSION_CODES.V" in android.telephony.SubscriptionManager.OnSubscriptionsChangedListener [101]
-android/telephony/SubscriptionPlan.java:134: lint: Unresolved link/see tag "android.telephony.Annotation.NetworkType NetworkType" in android.telephony.SubscriptionPlan [101]
-android/telephony/SubscriptionPlan.java:288: lint: Unresolved link/see tag "android.telephony.Annotation.NetworkType NetworkType" in android.telephony.SubscriptionPlan.Builder [101]
-android/telephony/TelephonyCallback.java:113: lint: Unresolved link/see tag "android.telephony.PreciseDisconnectCause PreciseDisconnectCause" in android.telephony.TelephonyCallback.CallDisconnectCauseListener [101]
-android/telephony/TelephonyManager.java:2544: lint: Unresolved link/see tag "android.telephony.TelephonyManager.CarrierRestrictionStatus CarrierRestrictionStatus" in android.telephony.TelephonyManager [101]
-android/telephony/TelephonyManager.java:2841: lint: Unresolved link/see tag "android.telephony.TelephonyManager.SetOpportunisticSubscriptionResult TelephonyManager.SetOpportunisticSubscriptionResult" in android.telephony.TelephonyManager [101]
-android/telephony/TelephonyManager.java:3273: lint: Unresolved link/see tag "android.telephony.TelephonyManager.PurchasePremiumCapabilityResult PurchasePremiumCapabilityResult" in android.telephony.TelephonyManager [101]
-android/telephony/TelephonyManager.java:3989: lint: Unresolved link/see tag "android.telephony.TelephonyManager.MobileDataPolicy MobileDataPolicy" in android.telephony.TelephonyManager [101]
-android/telephony/data/ApnSetting.java:39: lint: Unresolved link/see tag "android.telephony.data.DataCallResponse#getMtuV4() DataCallResponse#getMtuV4()" in android.telephony.data.ApnSetting [101]
-android/telephony/data/ApnSetting.java:50: lint: Unresolved link/see tag "android.telephony.data.DataCallResponse#getMtuV6() DataCallResponse#getMtuV6()" in android.telephony.data.ApnSetting [101]
-android/telephony/data/ApnSetting.java:597: lint: Unresolved link/see tag "android.telephony.data.DataCallResponse#getMtuV4() DataCallResponse#getMtuV4()" in android.telephony.data.ApnSetting.Builder [101]
-android/telephony/data/ApnSetting.java:611: lint: Unresolved link/see tag "android.telephony.data.DataCallResponse#getMtuV6() DataCallResponse#getMtuV6()" in android.telephony.data.ApnSetting.Builder [101]
-android/telephony/euicc/EuiccManager.java:331: lint: Unresolved link/see tag "android.service.euicc.EuiccService#ACTION_BIND_CARRIER_PROVISIONING_SERVICE" in android.telephony.euicc.EuiccManager [101]
-android/telephony/ims/ImsMmTelManager.java:117: lint: Unresolved link/see tag "android.telephony.ims.ImsService ImsService" in android.telephony.ims.ImsMmTelManager [101]
-android/telephony/ims/ImsRcsManager.java:43: lint: Unresolved link/see tag "android.telephony.ims.ImsService ImsService" in android.telephony.ims.ImsRcsManager [101]
-android/telephony/ims/ProvisioningManager.java:102: lint: Unresolved link/see tag "android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability MmTelFeature.MmTelCapabilities.MmTelCapability" in android.telephony.ims.ProvisioningManager [101]
-android/telephony/ims/ProvisioningManager.java:102: lint: Unresolved link/see tag "android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech ImsRegistrationImplBase.ImsRegistrationTech" in android.telephony.ims.ProvisioningManager [101]
-android/telephony/ims/ProvisioningManager.java:136: lint: Unresolved link/see tag "android.telephony.ims.ImsRcsManager.RcsImsCapabilityFlag ImsRcsManager.RcsImsCapabilityFlag" in android.telephony.ims.ProvisioningManager [101]
-android/telephony/ims/RegistrationManager.java:21: lint: Unresolved link/see tag "android.telephony.ims.feature.ImsFeature ImsFeature" in android.telephony.ims.RegistrationManager [101]
-android/telephony/ims/RegistrationManager.java:24: lint: Unresolved link/see tag "android.telephony.ims.ImsService ImsService" in android.telephony.ims.RegistrationManager [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]
-android/content/pm/ActivityInfo.java:1197: lint: Unresolved link/see tag "android.view.ViewRootImpl" in android.content.pm.ActivityInfo [101]
 android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101]
 android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware#enabledSinceTargetSdkVersion" in android.os.UserManager [101]
 android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "#initialize( PersistableBundle, SharedMemory, SoundTrigger.ModuleProperties)" in android.service.voice.AlwaysOnHotwordDetector [101]
@@ -300,22 +160,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/telephony/TelephonyRegistryManager.java:242: lint: Unresolved link/see tag "#listenFromListener" 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]
@@ -323,14 +172,3 @@
 com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "PackageInstaller.SessionParams#setRequestUpdateOwnership(boolean)" in android [101]
 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/telephony/SubscriptionManager.java:1370: lint: Unresolved link/see tag "Build.VERSION_CODES.Q" in android.telephony.SubscriptionManager.OnSubscriptionsChangedListener [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/config/preloaded-classes b/config/preloaded-classes
index aa34bad..7f8f5e3 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -6519,12 +6519,6 @@
 android.security.attestationverification.AttestationVerificationManager
 android.security.keymaster.ExportResult$1
 android.security.keymaster.ExportResult
-android.security.keymaster.IKeyAttestationApplicationIdProvider$Stub
-android.security.keymaster.IKeyAttestationApplicationIdProvider
-android.security.keymaster.KeyAttestationApplicationId$1
-android.security.keymaster.KeyAttestationApplicationId
-android.security.keymaster.KeyAttestationPackageInfo$1
-android.security.keymaster.KeyAttestationPackageInfo
 android.security.keymaster.KeyCharacteristics$1
 android.security.keymaster.KeyCharacteristics
 android.security.keymaster.KeymasterArgument$1
@@ -6549,7 +6543,13 @@
 android.security.keystore.BackendBusyException
 android.security.keystore.DelegatingX509Certificate
 android.security.keystore.DeviceIdAttestationException
+android.security.keystore.IKeyAttestationApplicationIdProvider$Stub
+android.security.keystore.IKeyAttestationApplicationIdProvider
+android.security.keystore.KeyAttestationApplicationId$Stub
+android.security.keystore.KeyAttestationApplicationId
 android.security.keystore.KeyAttestationException
+android.security.keystore.KeyAttestationPackageInfo$Stub
+android.security.keystore.KeyAttestationPackageInfo
 android.security.keystore.KeyExpiredException
 android.security.keystore.KeyGenParameterSpec$Builder
 android.security.keystore.KeyGenParameterSpec
@@ -6572,6 +6572,8 @@
 android.security.keystore.KeystoreResponse
 android.security.keystore.ParcelableKeyGenParameterSpec$1
 android.security.keystore.ParcelableKeyGenParameterSpec
+android.security.keystore.Signature$Stub
+android.security.keystore.Signature
 android.security.keystore.SecureKeyImportUnavailableException
 android.security.keystore.StrongBoxUnavailableException
 android.security.keystore.UserAuthArgs
diff --git a/core/api/current.txt b/core/api/current.txt
index d037c31..dfe023a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5739,6 +5739,7 @@
     ctor @Deprecated public FragmentBreadCrumbs(android.content.Context, android.util.AttributeSet);
     ctor @Deprecated public FragmentBreadCrumbs(android.content.Context, android.util.AttributeSet, int);
     method @Deprecated public void onBackStackChanged();
+    method @Deprecated protected void onLayout(boolean, int, int, int, int);
     method @Deprecated public void setActivity(android.app.Activity);
     method @Deprecated public void setMaxVisible(int);
     method @Deprecated public void setOnBreadCrumbClickListener(android.app.FragmentBreadCrumbs.OnBreadCrumbClickListener);
@@ -9683,7 +9684,7 @@
     method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @NonNull public int[] getDisplayIds();
     method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public CharSequence getDisplayName();
     method @Nullable public String getName();
-    method @Nullable public String getPersistentDeviceId();
+    method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public String getPersistentDeviceId();
     method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public boolean hasCustomSensorSupport();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDevice> CREATOR;
@@ -10992,6 +10993,7 @@
     field public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED";
     field public static final String ACTION_PACKAGE_REPLACED = "android.intent.action.PACKAGE_REPLACED";
     field public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
+    field @FlaggedApi("android.content.pm.stay_stopped") public static final String ACTION_PACKAGE_UNSTOPPED = "android.intent.action.PACKAGE_UNSTOPPED";
     field public static final String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED";
     field public static final String ACTION_PASTE = "android.intent.action.PASTE";
     field public static final String ACTION_PICK = "android.intent.action.PICK";
@@ -12673,6 +12675,7 @@
     method public boolean isDeviceUpgrading();
     method public abstract boolean isInstantApp();
     method public abstract boolean isInstantApp(@NonNull String);
+    method @FlaggedApi("android.content.pm.stay_stopped") public boolean isPackageStopped(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public boolean isPackageSuspended(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public boolean isPackageSuspended();
     method @CheckResult public abstract boolean isPermissionRevokedByPolicy(@NonNull String, @NonNull String);
@@ -13649,6 +13652,7 @@
 
   public interface XmlResourceParser extends org.xmlpull.v1.XmlPullParser android.util.AttributeSet java.lang.AutoCloseable {
     method public void close();
+    method public String getAttributeNamespace(int);
   }
 
 }
@@ -14348,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();
@@ -14366,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);
@@ -14594,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;
@@ -15667,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();
@@ -16086,10 +16090,12 @@
     method public String getFontFeatureSettings();
     method public float getFontMetrics(android.graphics.Paint.FontMetrics);
     method public android.graphics.Paint.FontMetrics getFontMetrics();
+    method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void getFontMetricsForLocale(@NonNull android.graphics.Paint.FontMetrics);
     method public void getFontMetricsInt(@NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean, @NonNull android.graphics.Paint.FontMetricsInt);
     method public void getFontMetricsInt(@NonNull char[], @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean, @NonNull android.graphics.Paint.FontMetricsInt);
     method public int getFontMetricsInt(android.graphics.Paint.FontMetricsInt);
     method public android.graphics.Paint.FontMetricsInt getFontMetricsInt();
+    method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void getFontMetricsIntForLocale(@NonNull android.graphics.Paint.FontMetricsInt);
     method public float getFontSpacing();
     method public String getFontVariationSettings();
     method public int getHinting();
@@ -16303,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();
@@ -17666,6 +17672,7 @@
     field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1
     field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0
     field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2
+    field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int LINE_BREAK_STYLE_NO_BREAK = 4; // 0x4
     field public static final int LINE_BREAK_STYLE_STRICT = 3; // 0x3
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1; // 0xffffffff
     field public static final int LINE_BREAK_WORD_STYLE_NONE = 0; // 0x0
@@ -20398,7 +20405,7 @@
     method @Deprecated public boolean isPreviewEnabled();
     method @Deprecated public boolean isProximityCorrectionEnabled();
     method @Deprecated public boolean isShifted();
-    method public void onClick(android.view.View);
+    method @Deprecated public void onClick(android.view.View);
     method @Deprecated public void onDetachedFromWindow();
     method @Deprecated public void onDraw(android.graphics.Canvas);
     method @Deprecated protected boolean onLongPress(android.inputmethodservice.Keyboard.Key);
@@ -24225,7 +24232,7 @@
 
   @Deprecated public class RemoteControlClient.MetadataEditor extends android.media.MediaMetadataEditor {
     method @Deprecated public void apply();
-    method public Object clone() throws java.lang.CloneNotSupportedException;
+    method @Deprecated public Object clone() throws java.lang.CloneNotSupportedException;
     method @Deprecated public android.media.RemoteControlClient.MetadataEditor putBitmap(int, android.graphics.Bitmap) throws java.lang.IllegalArgumentException;
     method @Deprecated public android.media.RemoteControlClient.MetadataEditor putLong(int, long) throws java.lang.IllegalArgumentException;
     method @Deprecated public android.media.RemoteControlClient.MetadataEditor putObject(int, Object) throws java.lang.IllegalArgumentException;
@@ -27089,6 +27096,7 @@
     method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();
     method public String getSelectedTrack(int);
     method public java.util.List<android.media.tv.TvTrackInfo> getTracks(int);
+    method protected void onLayout(boolean, int, int, int, int);
     method public boolean onUnhandledInputEvent(android.view.InputEvent);
     method public void overrideTvAppAttributionSource(@NonNull android.content.AttributionSource);
     method public void reset();
@@ -28562,6 +28570,8 @@
     method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
     method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
     method public boolean isEnabled();
+    method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled();
+    method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
     method public boolean isSecureNfcEnabled();
     method public boolean isSecureNfcSupported();
     field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
@@ -31971,6 +31981,7 @@
     field public static final int BATTERY_PROPERTY_CURRENT_AVERAGE = 3; // 0x3
     field public static final int BATTERY_PROPERTY_CURRENT_NOW = 2; // 0x2
     field public static final int BATTERY_PROPERTY_ENERGY_COUNTER = 5; // 0x5
+    field @FlaggedApi("android.os.state_of_health_public") public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10; // 0xa
     field public static final int BATTERY_PROPERTY_STATUS = 6; // 0x6
     field public static final int BATTERY_STATUS_CHARGING = 2; // 0x2
     field public static final int BATTERY_STATUS_DISCHARGING = 3; // 0x3
@@ -33084,6 +33095,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();
@@ -33646,6 +33673,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[]);
@@ -41554,7 +41583,7 @@
     method public android.telecom.GatewayInfo getGatewayInfo();
     method public android.net.Uri getHandle();
     method public int getHandlePresentation();
-    method @NonNull public String getId();
+    method @FlaggedApi("com.android.server.telecom.flags.call_details_id_changes") @NonNull public String getId();
     method public android.os.Bundle getIntentExtras();
     method public final int getState();
     method public android.telecom.StatusHints getStatusHints();
@@ -43521,7 +43550,6 @@
     method public int getLongitude();
     method public int getNetworkId();
     method public int getSystemId();
-    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR;
   }
 
@@ -43537,7 +43565,6 @@
     method @Nullable public String getMncString();
     method @Nullable public String getMobileNetworkOperator();
     method @Deprecated public int getPsc();
-    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityGsm> CREATOR;
   }
 
@@ -43555,7 +43582,6 @@
     method @Nullable public String getMobileNetworkOperator();
     method public int getPci();
     method public int getTac();
-    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityLte> CREATOR;
   }
 
@@ -43568,7 +43594,6 @@
     method @IntRange(from=0, to=3279165) public int getNrarfcn();
     method @IntRange(from=0, to=1007) public int getPci();
     method @IntRange(from=0, to=16777215) public int getTac();
-    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityNr> CREATOR;
   }
 
@@ -43582,7 +43607,6 @@
     method @Nullable public String getMncString();
     method @Nullable public String getMobileNetworkOperator();
     method public int getUarfcn();
-    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityTdscdma> CREATOR;
   }
 
@@ -43598,7 +43622,6 @@
     method @Nullable public String getMobileNetworkOperator();
     method public int getPsc();
     method public int getUarfcn();
-    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityWcdma> CREATOR;
   }
 
@@ -43682,6 +43705,7 @@
 
   public final class CellSignalStrengthCdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean equals(Object);
     method public int getAsuLevel();
     method public int getCdmaDbm();
     method public int getCdmaEcio();
@@ -43692,24 +43716,28 @@
     method public int getEvdoLevel();
     method public int getEvdoSnr();
     method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
+    method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthCdma> CREATOR;
   }
 
   public final class CellSignalStrengthGsm extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean equals(Object);
     method public int getAsuLevel();
     method public int getBitErrorRate();
     method public int getDbm();
     method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
     method public int getRssi();
     method public int getTimingAdvance();
+    method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthGsm> CREATOR;
   }
 
   public final class CellSignalStrengthLte extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean equals(Object);
     method public int getAsuLevel();
     method @IntRange(from=0, to=15) public int getCqi();
     method @IntRange(from=1, to=6) public int getCqiTableIndex();
@@ -43720,12 +43748,14 @@
     method public int getRssi();
     method public int getRssnr();
     method public int getTimingAdvance();
+    method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthLte> CREATOR;
   }
 
   public final class CellSignalStrengthNr extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean equals(Object);
     method public int getAsuLevel();
     method @IntRange(from=0, to=15) @NonNull public java.util.List<java.lang.Integer> getCsiCqiReport();
     method @IntRange(from=1, to=3) public int getCsiCqiTableIndex();
@@ -43738,26 +43768,31 @@
     method public int getSsRsrq();
     method public int getSsSinr();
     method @IntRange(from=0, to=1282) public int getTimingAdvanceMicros();
+    method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthNr> CREATOR;
   }
 
   public final class CellSignalStrengthTdscdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean equals(Object);
     method public int getAsuLevel();
     method public int getDbm();
     method @IntRange(from=0, to=4) public int getLevel();
     method public int getRscp();
+    method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthTdscdma> CREATOR;
   }
 
   public final class CellSignalStrengthWcdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean equals(Object);
     method public int getAsuLevel();
     method public int getDbm();
     method public int getEcNo();
     method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
+    method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthWcdma> CREATOR;
   }
@@ -44213,7 +44248,7 @@
     field public static final int OUT_OF_NETWORK = 11; // 0xb
     field public static final int OUT_OF_SERVICE = 18; // 0x12
     field public static final int POWER_OFF = 17; // 0x11
-    field public static final int SATELLITE_ENABLED = 82; // 0x52
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_ENABLED = 82; // 0x52
     field public static final int SERVER_ERROR = 12; // 0xc
     field public static final int SERVER_UNREACHABLE = 9; // 0x9
     field public static final int TIMED_OUT = 13; // 0xd
@@ -44322,7 +44357,7 @@
     method public boolean isNetworkRegistered();
     method public boolean isNetworkRoaming();
     method public boolean isNetworkSearching();
-    method public boolean isNonTerrestrialNetwork();
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public boolean isNonTerrestrialNetwork();
     method @Deprecated public boolean isRegistered();
     method @Deprecated public boolean isRoaming();
     method @Deprecated public boolean isSearching();
@@ -44338,7 +44373,7 @@
     field public static final int NR_STATE_RESTRICTED = 1; // 0x1
     field public static final int SERVICE_TYPE_DATA = 2; // 0x2
     field public static final int SERVICE_TYPE_EMERGENCY = 5; // 0x5
-    field public static final int SERVICE_TYPE_MMS = 6; // 0x6
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SERVICE_TYPE_MMS = 6; // 0x6
     field public static final int SERVICE_TYPE_SMS = 3; // 0x3
     field public static final int SERVICE_TYPE_UNKNOWN = 0; // 0x0
     field public static final int SERVICE_TYPE_VIDEO = 4; // 0x4
@@ -44553,7 +44588,7 @@
     method public boolean getRoaming();
     method public int getState();
     method public boolean isSearching();
-    method public boolean isUsingNonTerrestrialNetwork();
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public boolean isUsingNonTerrestrialNetwork();
     method public void setIsManualSelection(boolean);
     method public void setOperatorName(String, String, String);
     method public void setRoaming(boolean);
@@ -45339,7 +45374,7 @@
     field public static final int ERI_FLASH = 2; // 0x2
     field public static final int ERI_OFF = 1; // 0x1
     field public static final int ERI_ON = 0; // 0x0
-    field public static final String EVENT_DISPLAY_SOS_MESSAGE = "android.telephony.event.DISPLAY_SOS_MESSAGE";
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final String EVENT_DISPLAY_SOS_MESSAGE = "android.telephony.event.DISPLAY_SOS_MESSAGE";
     field public static final String EXTRA_ACTIVE_SIM_SUPPORTED_COUNT = "android.telephony.extra.ACTIVE_SIM_SUPPORTED_COUNT";
     field public static final String EXTRA_APN_PROTOCOL = "android.telephony.extra.APN_PROTOCOL";
     field public static final String EXTRA_APN_TYPE = "android.telephony.extra.APN_TYPE";
@@ -46545,6 +46580,7 @@
     method @Deprecated public int length();
     method @Deprecated public static android.text.AlteredCharSequence make(CharSequence, char[], int, int);
     method @Deprecated public CharSequence subSequence(int, int);
+    method @Deprecated public String toString();
   }
 
   @Deprecated public class AndroidCharacter {
@@ -46672,10 +46708,10 @@
     method @NonNull public android.text.DynamicLayout.Builder setHyphenationFrequency(int);
     method @NonNull public android.text.DynamicLayout.Builder setIncludePad(boolean);
     method @NonNull public android.text.DynamicLayout.Builder setJustificationMode(int);
-    method @NonNull public android.text.DynamicLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig);
+    method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public android.text.DynamicLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig);
     method @NonNull public android.text.DynamicLayout.Builder setLineSpacing(float, @FloatRange(from=0.0) float);
     method @NonNull public android.text.DynamicLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic);
-    method @NonNull public android.text.DynamicLayout.Builder setUseBoundsForWidth(boolean);
+    method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.DynamicLayout.Builder setUseBoundsForWidth(boolean);
     method @NonNull public android.text.DynamicLayout.Builder setUseLineSpacingFromFallbacks(boolean);
   }
 
@@ -46996,6 +47032,7 @@
     method public void removeSpan(Object);
     method public void setSpan(Object, int, int, int);
     method public CharSequence subSequence(int, int);
+    method public String toString();
   }
 
   public static final class PrecomputedText.Params {
@@ -47125,6 +47162,7 @@
     method public void setFilters(android.text.InputFilter[]);
     method public void setSpan(Object, int, int, int);
     method public CharSequence subSequence(int, int);
+    method public String toString();
     method public static android.text.SpannableStringBuilder valueOf(CharSequence);
   }
 
@@ -47199,7 +47237,7 @@
     method @NonNull public android.text.StaticLayout.Builder setMaxLines(@IntRange(from=0) int);
     method public android.text.StaticLayout.Builder setText(CharSequence);
     method @NonNull public android.text.StaticLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic);
-    method @NonNull public android.text.StaticLayout.Builder setUseBoundsForWidth(boolean);
+    method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.StaticLayout.Builder setUseBoundsForWidth(boolean);
     method @NonNull public android.text.StaticLayout.Builder setUseLineSpacingFromFallbacks(boolean);
   }
 
@@ -47912,6 +47950,10 @@
     method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig();
   }
 
+  @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final class LineBreakConfigSpan.NoBreakSpan extends android.text.style.LineBreakConfigSpan {
+    ctor @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public LineBreakConfigSpan.NoBreakSpan();
+  }
+
   @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final class LineBreakConfigSpan.NoHyphenationSpan extends android.text.style.LineBreakConfigSpan {
     ctor @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public LineBreakConfigSpan.NoHyphenationSpan();
   }
@@ -48761,7 +48803,9 @@
     method public boolean containsValue(Object);
     method public void ensureCapacity(int);
     method public java.util.Set<java.util.Map.Entry<K,V>> entrySet();
+    method public boolean equals(@Nullable Object);
     method public V get(Object);
+    method public int hashCode();
     method public int indexOfKey(Object);
     method public int indexOfValue(Object);
     method public boolean isEmpty();
@@ -48793,7 +48837,9 @@
     method public boolean contains(Object);
     method public boolean containsAll(java.util.Collection<?>);
     method public void ensureCapacity(int);
+    method public boolean equals(@Nullable Object);
     method public void forEach(java.util.function.Consumer<? super E>);
+    method public int hashCode();
     method public int indexOf(Object);
     method public boolean isEmpty();
     method public java.util.Iterator<E> iterator();
@@ -53115,6 +53161,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();
@@ -53184,6 +53231,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);
@@ -53533,6 +53581,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();
@@ -53542,6 +53591,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);
@@ -54652,7 +54702,6 @@
 
   public final class AutofillManager {
     method public void cancel();
-    method public void clearAutofillRequestCallback();
     method public void commit();
     method public void disableAutofillServices();
     method @Nullable public android.content.ComponentName getAutofillServiceComponentName();
@@ -54679,7 +54728,6 @@
     method public void registerCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
     method public void requestAutofill(@NonNull android.view.View);
     method public void requestAutofill(@NonNull android.view.View, int, @NonNull android.graphics.Rect);
-    method @RequiresPermission(android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS) public void setAutofillRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.autofill.AutofillRequestCallback);
     method public void setUserData(@Nullable android.service.autofill.UserData);
     method public boolean showAutofillDialog(@NonNull android.view.View);
     method public boolean showAutofillDialog(@NonNull android.view.View, int);
@@ -54700,10 +54748,6 @@
     field public static final int EVENT_INPUT_UNAVAILABLE = 3; // 0x3
   }
 
-  public interface AutofillRequestCallback {
-    method public void onFillRequest(@Nullable android.view.inputmethod.InlineSuggestionsRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback);
-  }
-
   public final class AutofillValue implements android.os.Parcelable {
     method public int describeContents();
     method public static android.view.autofill.AutofillValue forDate(long);
@@ -55155,12 +55199,10 @@
     ctor public InlineSuggestionsRequest.Builder(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder addInlinePresentationSpecs(@NonNull android.widget.inline.InlinePresentationSpec);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest build();
-    method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setClientSupported(boolean);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setExtras(@NonNull android.os.Bundle);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlineTooltipPresentationSpec(@NonNull android.widget.inline.InlinePresentationSpec);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setMaxSuggestionCount(int);
-    method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setServiceSupported(boolean);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setSupportedLocales(@NonNull android.os.LocaleList);
   }
 
@@ -57536,6 +57578,7 @@
     ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet);
     ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet, int);
     ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet, int, int);
+    method @Deprecated protected void onLayout(boolean, int, int, int, int);
   }
 
   @Deprecated public static class AbsoluteLayout.LayoutParams extends android.view.ViewGroup.LayoutParams {
@@ -57614,6 +57657,7 @@
     method public long getSelectedItemId();
     method public int getSelectedItemPosition();
     method public abstract android.view.View getSelectedView();
+    method protected void onLayout(boolean, int, int, int, int);
     method public boolean performItemClick(android.view.View, int, long);
     method public abstract void setAdapter(T);
     method public void setEmptyView(android.view.View);
@@ -58260,6 +58304,7 @@
     method public android.widget.FrameLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
     method @Deprecated public boolean getConsiderGoneChildrenWhenMeasuring();
     method public boolean getMeasureAllChildren();
+    method protected void onLayout(boolean, int, int, int, int);
     method public void setMeasureAllChildren(boolean);
   }
 
@@ -58313,6 +58358,7 @@
     method public boolean getUseDefaultMargins();
     method public boolean isColumnOrderPreserved();
     method public boolean isRowOrderPreserved();
+    method protected void onLayout(boolean, int, int, int, int);
     method public void setAlignmentMode(int);
     method public void setColumnCount(int);
     method public void setColumnOrderPreserved(boolean);
@@ -58535,6 +58581,7 @@
     method public float getWeightSum();
     method public boolean isBaselineAligned();
     method public boolean isMeasureWithLargestChildEnabled();
+    method protected void onLayout(boolean, int, int, int, int);
     method public void setBaselineAligned(boolean);
     method public void setBaselineAlignedChildIndex(int);
     method public void setDividerDrawable(android.graphics.drawable.Drawable);
@@ -59083,6 +59130,7 @@
     method public android.widget.RelativeLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
     method public int getGravity();
     method public int getIgnoreGravity();
+    method protected void onLayout(boolean, int, int, int, int);
     method public void setGravity(int);
     method public void setHorizontalGravity(int);
     method public void setIgnoreGravity(int);
@@ -59537,6 +59585,7 @@
     method @Deprecated public boolean isMoving();
     method @Deprecated public boolean isOpened();
     method @Deprecated public void lock();
+    method @Deprecated protected void onLayout(boolean, int, int, int, int);
     method @Deprecated public void open();
     method @Deprecated public void setOnDrawerCloseListener(android.widget.SlidingDrawer.OnDrawerCloseListener);
     method @Deprecated public void setOnDrawerOpenListener(android.widget.SlidingDrawer.OnDrawerOpenListener);
@@ -60183,6 +60232,7 @@
     method public boolean hideOverflowMenu();
     method public void inflateMenu(@MenuRes int);
     method public boolean isOverflowMenuShowing();
+    method protected void onLayout(boolean, int, int, int, int);
     method public void setCollapseContentDescription(@StringRes int);
     method public void setCollapseContentDescription(@Nullable CharSequence);
     method public void setCollapseIcon(@DrawableRes int);
@@ -60337,7 +60387,7 @@
     method @Deprecated public android.view.View getZoomControls();
     method @Deprecated public boolean isAutoDismissed();
     method @Deprecated public boolean isVisible();
-    method public boolean onTouch(android.view.View, android.view.MotionEvent);
+    method @Deprecated public boolean onTouch(android.view.View, android.view.MotionEvent);
     method @Deprecated public void setAutoDismissed(boolean);
     method @Deprecated public void setFocusable(boolean);
     method @Deprecated public void setOnZoomListener(android.widget.ZoomButtonsController.OnZoomListener);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 052d614..b5d3ed7 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -6,9 +6,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 f825650..9ecce14 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -297,6 +297,8 @@
     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";
     field public static final String RECOVERY = "android.permission.RECOVERY";
@@ -310,7 +312,7 @@
     field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES";
     field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
     field public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS";
-    field public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS";
+    field @FlaggedApi("backstage_power.report_usage_stats_permission") public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS";
     field @Deprecated public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
     field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
     field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
@@ -652,7 +654,6 @@
     field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio";
     field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast";
     field public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = "android:receive_explicit_user_interaction_audio";
-    field public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO = "android:receive_sandbox_trigger_audio";
     field public static final String OPSTR_REQUEST_DELETE_PACKAGES = "android:request_delete_packages";
     field public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages";
     field public static final String OPSTR_RUN_ANY_IN_BACKGROUND = "android:run_any_in_background";
@@ -3209,7 +3210,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.input.VirtualTouchscreenConfig);
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method public int getDeviceId();
-    method @Nullable public String getPersistentDeviceId();
+    method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public String getPersistentDeviceId();
     method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensor> getVirtualSensorList();
     method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
@@ -4058,7 +4059,9 @@
   }
 
   public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable {
+    method public int describeContents();
     method public void onUninstallComplete(@NonNull String, int, @Nullable String);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.UninstallCompleteCallback> CREATOR;
   }
 
@@ -4529,7 +4532,7 @@
     method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
     method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String);
     method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float);
-    field public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 128; // 0x80
+    field @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 128; // 0x80
     field public static final int VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED = 65536; // 0x10000
     field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400
   }
@@ -5958,6 +5961,7 @@
 
   public static final class SoundTrigger.Keyphrase implements android.os.Parcelable {
     ctor public SoundTrigger.Keyphrase(int, int, @NonNull java.util.Locale, @NonNull String, @Nullable int[]);
+    method public int describeContents();
     method public int getId();
     method @NonNull public java.util.Locale getLocale();
     method public int getRecognitionModes();
@@ -5980,6 +5984,7 @@
   public static final class SoundTrigger.KeyphraseSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable {
     ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[], int);
     ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[]);
+    method public int describeContents();
     method @NonNull public android.hardware.soundtrigger.SoundTrigger.Keyphrase[] getKeyphrases();
     method @NonNull public static android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel readFromParcel(@NonNull android.os.Parcel);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -5987,6 +5992,7 @@
   }
 
   public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable {
+    method public int describeContents();
     method public int getEnd();
     method public int getStart();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -6797,6 +6803,7 @@
 
   public abstract class MusicRecognitionService extends android.app.Service {
     ctor public MusicRecognitionService();
+    method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
     method public abstract void onRecognize(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @NonNull android.media.musicrecognition.MusicRecognitionService.Callback);
   }
 
@@ -6855,6 +6862,7 @@
 
   public abstract class SoundTriggerDetectionService extends android.app.Service {
     ctor public SoundTriggerDetectionService();
+    method public final android.os.IBinder onBind(android.content.Intent);
     method @MainThread public void onConnected(@NonNull java.util.UUID, @Nullable android.os.Bundle);
     method @MainThread public void onDisconnected(@NonNull java.util.UUID, @Nullable android.os.Bundle);
     method @MainThread public void onError(@NonNull java.util.UUID, @Nullable android.os.Bundle, int, int);
@@ -7568,6 +7576,7 @@
   }
 
   public class MediaEvent extends android.media.tv.tuner.filter.FilterEvent {
+    method protected void finalize();
     method public long getAudioHandle();
     method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();
     method public long getAvDataId();
@@ -8962,6 +8971,8 @@
 package android.net.metrics {
 
   @Deprecated public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
   }
 
   @Deprecated public static final class ApfProgramEvent.Builder {
@@ -8976,6 +8987,8 @@
   }
 
   @Deprecated public final class ApfStats implements android.net.metrics.IpConnectivityLog.Event {
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
   }
 
   @Deprecated public static final class ApfStats.Builder {
@@ -8994,6 +9007,8 @@
   }
 
   @Deprecated public final class DhcpClientEvent implements android.net.metrics.IpConnectivityLog.Event {
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
   }
 
   @Deprecated public static final class DhcpClientEvent.Builder {
@@ -9005,7 +9020,9 @@
 
   @Deprecated public final class DhcpErrorEvent implements android.net.metrics.IpConnectivityLog.Event {
     ctor @Deprecated public DhcpErrorEvent(int);
+    method @Deprecated public int describeContents();
     method @Deprecated public static int errorCodeWithOption(int, int);
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
     field @Deprecated public static final int BOOTP_TOO_SHORT = 67174400; // 0x4010000
     field @Deprecated public static final int BUFFER_UNDERFLOW = 83951616; // 0x5010000
     field @Deprecated public static final int DHCP_BAD_MAGIC_COOKIE = 67239936; // 0x4020000
@@ -9043,6 +9060,8 @@
 
   @Deprecated public final class IpManagerEvent implements android.net.metrics.IpConnectivityLog.Event {
     ctor @Deprecated public IpManagerEvent(int, long);
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
     field @Deprecated public static final int COMPLETE_LIFECYCLE = 3; // 0x3
     field @Deprecated public static final int ERROR_INTERFACE_NOT_FOUND = 8; // 0x8
     field @Deprecated public static final int ERROR_INVALID_PROVISIONING = 7; // 0x7
@@ -9055,6 +9074,8 @@
 
   @Deprecated public final class IpReachabilityEvent implements android.net.metrics.IpConnectivityLog.Event {
     ctor @Deprecated public IpReachabilityEvent(int);
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
     field @Deprecated public static final int NUD_FAILED = 512; // 0x200
     field @Deprecated public static final int NUD_FAILED_ORGANIC = 1024; // 0x400
     field @Deprecated public static final int PROBE = 256; // 0x100
@@ -9065,6 +9086,8 @@
   @Deprecated public final class NetworkEvent implements android.net.metrics.IpConnectivityLog.Event {
     ctor @Deprecated public NetworkEvent(int, long);
     ctor @Deprecated public NetworkEvent(int);
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
     field @Deprecated public static final int NETWORK_CAPTIVE_PORTAL_FOUND = 4; // 0x4
     field @Deprecated public static final int NETWORK_CONNECTED = 1; // 0x1
     field @Deprecated public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12; // 0xc
@@ -9081,6 +9104,8 @@
   }
 
   @Deprecated public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event {
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
   }
 
   @Deprecated public static final class RaEvent.Builder {
@@ -9095,7 +9120,9 @@
   }
 
   @Deprecated public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event {
+    method @Deprecated public int describeContents();
     method @Deprecated @NonNull public static String getProbeName(int);
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
     field @Deprecated public static final int DNS_FAILURE = 0; // 0x0
     field @Deprecated public static final int DNS_SUCCESS = 1; // 0x1
     field @Deprecated public static final int PROBE_DNS = 0; // 0x0
@@ -9605,6 +9632,7 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable();
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
+    method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
@@ -9704,7 +9732,6 @@
     field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9; // 0x9
     field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_FIRST_USAGE_DATE = 8; // 0x8
     field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_MANUFACTURING_DATE = 7; // 0x7
-    field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10; // 0xa
     field public static final int CHARGING_POLICY_ADAPTIVE_AC = 3; // 0x3
     field public static final int CHARGING_POLICY_ADAPTIVE_AON = 2; // 0x2
     field public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE = 4; // 0x4
@@ -9772,8 +9799,8 @@
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanResults(@NonNull android.os.WorkSource, int);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStarted(@NonNull android.os.WorkSource, boolean);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStopped(@NonNull android.os.WorkSource, boolean);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int, int, @NonNull String);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int, int, @NonNull String);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int, int, @NonNull String);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int, int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockAcquiredFromSource(@NonNull android.os.WorkSource);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockReleasedFromSource(@NonNull android.os.WorkSource);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportMobileRadioPowerState(boolean, int);
@@ -11500,6 +11527,7 @@
 
   public abstract class FieldClassificationService extends android.app.Service {
     ctor public FieldClassificationService();
+    method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract void onClassificationRequest(@NonNull android.service.assist.classification.FieldClassificationRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.assist.classification.FieldClassificationResponse,java.lang.Exception>);
     method public void onConnected();
     method public void onDisconnected();
@@ -11578,6 +11606,7 @@
     method protected final void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]);
     method protected void dump(@NonNull java.io.PrintWriter, @NonNull String[]);
     method @Nullable public final android.service.autofill.FillEventHistory getFillEventHistory();
+    method public final android.os.IBinder onBind(android.content.Intent);
     method public void onConnected();
     method public void onDisconnected();
     method public void onFillRequest(@NonNull android.service.autofill.augmented.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.augmented.FillController, @NonNull android.service.autofill.augmented.FillCallback);
@@ -11616,6 +11645,7 @@
 
   public final class FillWindow implements java.lang.AutoCloseable {
     ctor public FillWindow();
+    method public void close();
     method public void destroy();
     method public boolean update(@NonNull android.service.autofill.augmented.PresentationParams.Area, @NonNull android.view.View, long);
   }
@@ -11641,6 +11671,7 @@
   public final class CarrierMessagingServiceWrapper implements java.lang.AutoCloseable {
     ctor public CarrierMessagingServiceWrapper();
     method public boolean bindToCarrierMessagingService(@NonNull android.content.Context, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull Runnable);
+    method public void close();
     method public void disconnect();
     method public void downloadMms(@NonNull android.net.Uri, int, @NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallback);
     method public void receiveSms(@NonNull android.service.carrier.MessagePdu, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallback);
@@ -11691,6 +11722,7 @@
     method public final void disableSelf();
     method public void onActivityEvent(@NonNull android.service.contentcapture.ActivityEvent);
     method public void onActivitySnapshot(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.service.contentcapture.SnapshotData);
+    method public final android.os.IBinder onBind(android.content.Intent);
     method public void onConnected();
     method public void onContentCaptureEvent(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.view.contentcapture.ContentCaptureEvent);
     method public void onCreateContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureContext, @NonNull android.view.contentcapture.ContentCaptureSessionId);
@@ -11729,6 +11761,7 @@
 
   public abstract class ContentSuggestionsService extends android.app.Service {
     ctor public ContentSuggestionsService();
+    method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract void onClassifyContentSelections(@NonNull android.app.contentsuggestions.ClassificationsRequest, @NonNull android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback);
     method public abstract void onNotifyInteraction(@NonNull String, @NonNull android.os.Bundle);
     method public abstract void onProcessContextImage(int, @Nullable android.graphics.Bitmap, @NonNull android.os.Bundle);
@@ -11742,6 +11775,7 @@
 
   public abstract class DataLoaderService extends android.app.Service {
     ctor public DataLoaderService();
+    method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader(@NonNull android.content.pm.DataLoaderParams);
   }
 
@@ -12414,6 +12448,7 @@
 
   public class TraceReportService extends android.app.Service {
     ctor public TraceReportService();
+    method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method public void onReportTrace(@NonNull android.service.tracing.TraceReportService.TraceParams);
   }
 
@@ -13039,6 +13074,7 @@
     method @Nullable public android.content.ComponentName getCallScreeningComponent();
     method public boolean isBlocked();
     method public boolean isInContacts();
+    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telecom.Connection.CallFilteringCompletionInfo> CREATOR;
   }
 
@@ -13228,7 +13264,7 @@
     method public void requestStreamingState(int);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telecom.StreamingCall> CREATOR;
-    field public static final String EXTRA_CALL_ID = "android.telecom.extra.CALL_ID";
+    field @FlaggedApi("com.android.server.telecom.flags.call_details_id_changes") public static final String EXTRA_CALL_ID = "android.telecom.extra.CALL_ID";
     field public static final int STATE_DISCONNECTED = 3; // 0x3
     field public static final int STATE_HOLDING = 2; // 0x2
     field public static final int STATE_STREAMING = 1; // 0x1
@@ -13348,6 +13384,7 @@
     method public int getReason();
     method public int getTimeoutSeconds();
     method public boolean isEnabled();
+    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR;
     field public static final int REASON_ALL = 4; // 0x4
     field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5
@@ -13632,6 +13669,7 @@
     method public int getDownlinkCapacityKbps();
     method public int getType();
     method public int getUplinkCapacityKbps();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.LinkCapacityEstimate> CREATOR;
     field public static final int INVALID = -1; // 0xffffffff
     field public static final int LCE_TYPE_COMBINED = 2; // 0x2
@@ -13641,8 +13679,10 @@
 
   public final class LteVopsSupportInfo extends android.telephony.VopsSupportInfo {
     ctor public LteVopsSupportInfo(int, int);
+    method public boolean equals(@Nullable Object);
     method public int getEmcBearerSupport();
     method public int getVopsSupport();
+    method public int hashCode();
     method public boolean isEmergencyServiceFallbackSupported();
     method public boolean isEmergencyServiceSupported();
     method public boolean isVopsSupported();
@@ -13709,7 +13749,7 @@
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setCellIdentity(@Nullable android.telephony.CellIdentity);
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setDomain(int);
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setEmergencyOnly(boolean);
-    method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setIsNonTerrestrialNetwork(boolean);
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull public android.telephony.NetworkRegistrationInfo.Builder setIsNonTerrestrialNetwork(boolean);
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRegisteredPlmn(@Nullable String);
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRegistrationState(int);
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRejectCause(int);
@@ -13743,9 +13783,11 @@
 
   public final class NrVopsSupportInfo extends android.telephony.VopsSupportInfo {
     ctor public NrVopsSupportInfo(int, int, int);
+    method public boolean equals(@Nullable Object);
     method public int getEmcSupport();
     method public int getEmfSupport();
     method public int getVopsSupport();
+    method public int hashCode();
     method public boolean isEmergencyServiceFallbackSupported();
     method public boolean isEmergencyServiceSupported();
     method public boolean isVopsSupported();
@@ -14858,6 +14900,7 @@
 
   public abstract class QualifiedNetworksService extends android.app.Service {
     ctor public QualifiedNetworksService();
+    method public android.os.IBinder onBind(android.content.Intent);
     method @NonNull public abstract android.telephony.data.QualifiedNetworksService.NetworkAvailabilityProvider onCreateNetworkAvailabilityProvider(int);
     field public static final String QUALIFIED_NETWORKS_SERVICE_INTERFACE = "android.telephony.data.QualifiedNetworksService";
   }
@@ -15049,6 +15092,7 @@
   public class GbaService extends android.app.Service {
     ctor public GbaService();
     method public void onAuthenticationRequest(int, int, int, @NonNull android.net.Uri, @NonNull byte[], boolean);
+    method public android.os.IBinder onBind(android.content.Intent);
     method public final void reportAuthenticationFailure(int, int) throws java.lang.RuntimeException;
     method public final void reportKeysAvailable(int, @NonNull byte[], @NonNull String) throws java.lang.RuntimeException;
     field public static final String SERVICE_INTERFACE = "android.telephony.gba.GbaService";
@@ -15551,6 +15595,7 @@
     method @Deprecated public android.telephony.ims.stub.ImsRegistrationImplBase getRegistration(int);
     method @NonNull public android.telephony.ims.stub.ImsRegistrationImplBase getRegistrationForSubscription(int, int);
     method @Nullable public android.telephony.ims.stub.SipTransportImplBase getSipTransport(int);
+    method public android.os.IBinder onBind(android.content.Intent);
     method public final void onUpdateSupportedImsFeatures(android.telephony.ims.stub.ImsFeatureConfiguration) throws android.os.RemoteException;
     method public android.telephony.ims.stub.ImsFeatureConfiguration querySupportedImsFeatures();
     method public void readyForFeatureCreation();
@@ -16679,157 +16724,157 @@
 
 package android.telephony.satellite {
 
-  public final class AntennaDirection implements android.os.Parcelable {
-    method public int describeContents();
-    method public float getX();
-    method public float getY();
-    method public float getZ();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaDirection> CREATOR;
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class AntennaDirection implements android.os.Parcelable {
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getX();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getY();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getZ();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaDirection> CREATOR;
   }
 
-  public final class AntennaPosition implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public android.telephony.satellite.AntennaDirection getAntennaDirection();
-    method public int getSuggestedHoldPosition();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaPosition> CREATOR;
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class AntennaPosition implements android.os.Parcelable {
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public android.telephony.satellite.AntennaDirection getAntennaDirection();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getSuggestedHoldPosition();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaPosition> CREATOR;
   }
 
-  public final class PointingInfo implements android.os.Parcelable {
-    method public int describeContents();
-    method public float getSatelliteAzimuthDegrees();
-    method public float getSatelliteElevationDegrees();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.PointingInfo> CREATOR;
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class PointingInfo implements android.os.Parcelable {
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getSatelliteAzimuthDegrees();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getSatelliteElevationDegrees();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.PointingInfo> CREATOR;
   }
 
-  public final class SatelliteCapabilities implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public java.util.Map<java.lang.Integer,android.telephony.satellite.AntennaPosition> getAntennaPositionMap();
-    method public int getMaxBytesPerOutgoingDatagram();
-    method @NonNull public java.util.Set<java.lang.Integer> getSupportedRadioTechnologies();
-    method public boolean isPointingRequired();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteCapabilities> CREATOR;
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteCapabilities implements android.os.Parcelable {
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public java.util.Map<java.lang.Integer,android.telephony.satellite.AntennaPosition> getAntennaPositionMap();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getMaxBytesPerOutgoingDatagram();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public java.util.Set<java.lang.Integer> getSupportedRadioTechnologies();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isPointingRequired();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteCapabilities> CREATOR;
   }
 
-  public final class SatelliteDatagram implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public byte[] getSatelliteDatagram();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteDatagram> CREATOR;
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteDatagram implements android.os.Parcelable {
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public byte[] getSatelliteDatagram();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteDatagram> CREATOR;
   }
 
-  public interface SatelliteDatagramCallback {
-    method public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>);
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteDatagramCallback {
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>);
   }
 
-  public final class SatelliteManager {
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteManager {
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatelliteService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatelliteService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsDemoModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsDemoModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteAttachEnabledForCarrier(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteCommunicationAllowedForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
-    method public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteCommunicationAllowedForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void sendSatelliteDatagram(int, @NonNull android.telephony.satellite.SatelliteDatagram, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startSatelliteTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopSatelliteTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteModemStateChanged(@NonNull android.telephony.satellite.SatelliteStateCallback);
-    method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
-    field public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2; // 0x2
-    field public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1; // 0x1
-    field public static final int DATAGRAM_TYPE_UNKNOWN = 0; // 0x0
-    field public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2; // 0x2
-    field public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3; // 0x3
-    field public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1; // 0x1
-    field public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0; // 0x0
-    field public static final int DISPLAY_MODE_CLOSED = 3; // 0x3
-    field public static final int DISPLAY_MODE_FIXED = 1; // 0x1
-    field public static final int DISPLAY_MODE_OPENED = 2; // 0x2
-    field public static final int DISPLAY_MODE_UNKNOWN = 0; // 0x0
-    field public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3; // 0x3
-    field public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1; // 0x1
-    field public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2
-    field public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4
-    field public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void sendSatelliteDatagram(int, @NonNull android.telephony.satellite.SatelliteDatagram, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startSatelliteTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopSatelliteTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteModemStateChanged(@NonNull android.telephony.satellite.SatelliteStateCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_UNKNOWN = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_CLOSED = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_FIXED = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_OPENED = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_UNKNOWN = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1
-    field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0
-    field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7
-    field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6
-    field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS = 5; // 0x5
-    field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 4; // 0x4
-    field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING = 1; // 0x1
-    field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; // 0x3
-    field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; // 0x2
-    field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; // 0xffffffff
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS = 5; // 0x5
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 4; // 0x4
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; // 0xffffffff
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT = 8; // 0x8
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_CONNECTED = 7; // 0x7
-    field public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3
-    field public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2
-    field public static final int SATELLITE_MODEM_STATE_IDLE = 0; // 0x0
-    field public static final int SATELLITE_MODEM_STATE_LISTENING = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_IDLE = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_LISTENING = 1; // 0x1
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_NOT_CONNECTED = 6; // 0x6
-    field public static final int SATELLITE_MODEM_STATE_OFF = 4; // 0x4
-    field public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; // 0x5
-    field public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff
-    field public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; // 0x10
-    field public static final int SATELLITE_RESULT_ERROR = 1; // 0x1
-    field public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; // 0x8
-    field public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; // 0x7
-    field public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6
-    field public static final int SATELLITE_RESULT_MODEM_BUSY = 22; // 0x16
-    field public static final int SATELLITE_RESULT_MODEM_ERROR = 4; // 0x4
-    field public static final int SATELLITE_RESULT_NETWORK_ERROR = 5; // 0x5
-    field public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17; // 0x11
-    field public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19; // 0x13
-    field public static final int SATELLITE_RESULT_NOT_REACHABLE = 18; // 0x12
-    field public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20; // 0x14
-    field public static final int SATELLITE_RESULT_NO_RESOURCES = 12; // 0xc
-    field public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10; // 0xa
-    field public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15; // 0xf
-    field public static final int SATELLITE_RESULT_REQUEST_FAILED = 9; // 0x9
-    field public static final int SATELLITE_RESULT_REQUEST_IN_PROGRESS = 21; // 0x15
-    field public static final int SATELLITE_RESULT_REQUEST_NOT_SUPPORTED = 11; // 0xb
-    field public static final int SATELLITE_RESULT_SERVER_ERROR = 2; // 0x2
-    field public static final int SATELLITE_RESULT_SERVICE_ERROR = 3; // 0x3
-    field public static final int SATELLITE_RESULT_SERVICE_NOT_PROVISIONED = 13; // 0xd
-    field public static final int SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS = 14; // 0xe
-    field public static final int SATELLITE_RESULT_SUCCESS = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_OFF = 4; // 0x4
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; // 0x5
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; // 0x10
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ERROR = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; // 0x8
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; // 0x7
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_BUSY = 22; // 0x16
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_ERROR = 4; // 0x4
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NETWORK_ERROR = 5; // 0x5
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17; // 0x11
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19; // 0x13
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_REACHABLE = 18; // 0x12
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20; // 0x14
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NO_RESOURCES = 12; // 0xc
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10; // 0xa
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15; // 0xf
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_FAILED = 9; // 0x9
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_IN_PROGRESS = 21; // 0x15
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_NOT_SUPPORTED = 11; // 0xb
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVER_ERROR = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVICE_ERROR = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVICE_NOT_PROVISIONED = 13; // 0xd
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS = 14; // 0xe
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SUCCESS = 0; // 0x0
   }
 
-  public static class SatelliteManager.SatelliteException extends java.lang.Exception {
-    ctor public SatelliteManager.SatelliteException(int);
-    method public int getErrorCode();
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static class SatelliteManager.SatelliteException extends java.lang.Exception {
+    ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public SatelliteManager.SatelliteException(int);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getErrorCode();
   }
 
-  public interface SatelliteProvisionStateCallback {
-    method public void onSatelliteProvisionStateChanged(boolean);
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteProvisionStateCallback {
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteProvisionStateChanged(boolean);
   }
 
-  public interface SatelliteStateCallback {
-    method public void onSatelliteModemStateChanged(int);
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteStateCallback {
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteModemStateChanged(int);
   }
 
-  public interface SatelliteTransmissionUpdateCallback {
-    method public void onReceiveDatagramStateChanged(int, int, int);
-    method public void onSatellitePositionChanged(@NonNull android.telephony.satellite.PointingInfo);
-    method public void onSendDatagramStateChanged(int, int, int);
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteTransmissionUpdateCallback {
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onReceiveDatagramStateChanged(int, int, int);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatellitePositionChanged(@NonNull android.telephony.satellite.PointingInfo);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSendDatagramStateChanged(int, int, int);
   }
 
 }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index db751a4..0e857a9 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -28,7 +28,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";
@@ -56,7 +55,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";
@@ -840,7 +838,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();
@@ -1181,6 +1179,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();
@@ -1903,7 +1902,7 @@
     method public android.media.PlaybackParams setAudioStretchMode(int);
   }
 
-  public final class RingtoneSelection {
+  @FlaggedApi("android.os.vibrator.haptics_customization_enabled") public final class RingtoneSelection {
     method @NonNull public static android.media.RingtoneSelection fromUri(@Nullable android.net.Uri, int);
     method public int getSoundSource();
     method @Nullable public android.net.Uri getSoundUri();
@@ -1915,14 +1914,16 @@
     field public static final int FROM_URI_RINGTONE_SELECTION_ONLY = 3; // 0x3
     field public static final int FROM_URI_RINGTONE_SELECTION_OR_SOUND = 1; // 0x1
     field public static final int FROM_URI_RINGTONE_SELECTION_OR_VIBRATION = 2; // 0x2
-    field public static final int SOUND_SOURCE_DEFAULT = 0; // 0x0
     field public static final int SOUND_SOURCE_OFF = 1; // 0x1
+    field public static final int SOUND_SOURCE_SYSTEM_DEFAULT = 3; // 0x3
+    field public static final int SOUND_SOURCE_UNSPECIFIED = 0; // 0x0
     field public static final int SOUND_SOURCE_URI = 2; // 0x2
-    field public static final int VIBRATION_SOURCE_APPLICATION_PROVIDED = 3; // 0x3
+    field public static final int VIBRATION_SOURCE_APPLICATION_DEFAULT = 4; // 0x4
     field public static final int VIBRATION_SOURCE_AUDIO_CHANNEL = 10; // 0xa
-    field public static final int VIBRATION_SOURCE_DEFAULT = 0; // 0x0
     field public static final int VIBRATION_SOURCE_HAPTIC_GENERATOR = 11; // 0xb
     field public static final int VIBRATION_SOURCE_OFF = 1; // 0x1
+    field public static final int VIBRATION_SOURCE_SYSTEM_DEFAULT = 3; // 0x3
+    field public static final int VIBRATION_SOURCE_UNSPECIFIED = 0; // 0x0
     field public static final int VIBRATION_SOURCE_URI = 2; // 0x2
   }
 
@@ -2610,17 +2611,17 @@
 
 package android.os.vibrator.persistence {
 
-  public class ParsedVibration {
+  @FlaggedApi("android.os.vibrator.enable_vibration_serialization_apis") public class ParsedVibration {
     method @NonNull public java.util.List<android.os.VibrationEffect> getVibrationEffects();
     method @Nullable public android.os.VibrationEffect resolve(@NonNull android.os.Vibrator);
   }
 
-  public final class VibrationXmlParser {
+  @FlaggedApi("android.os.vibrator.enable_vibration_serialization_apis") public final class VibrationXmlParser {
     method @Nullable public static android.os.vibrator.persistence.ParsedVibration parseDocument(@NonNull java.io.Reader) throws java.io.IOException;
     method @Nullable public static android.os.VibrationEffect parseVibrationEffect(@NonNull java.io.Reader) throws java.io.IOException;
   }
 
-  public final class VibrationXmlSerializer {
+  @FlaggedApi("android.os.vibrator.enable_vibration_serialization_apis") public final class VibrationXmlSerializer {
     method public static void serialize(@NonNull android.os.VibrationEffect, @NonNull java.io.Writer) throws java.io.IOException, android.os.vibrator.persistence.VibrationXmlSerializer.SerializationFailedException;
   }
 
@@ -2955,10 +2956,6 @@
     method @Deprecated public boolean isBound();
   }
 
-  public class NotificationRankingUpdate implements android.os.Parcelable {
-    method public final boolean isFdNotNullAndClosed();
-  }
-
 }
 
 package android.service.quickaccesswallet {
@@ -3188,7 +3185,7 @@
     field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2
     field public static final int HAL_SERVICE_MODEM = 3; // 0x3
     field public static final int HAL_SERVICE_NETWORK = 4; // 0x4
-    field public static final int HAL_SERVICE_SATELLITE = 8; // 0x8
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int HAL_SERVICE_SATELLITE = 8; // 0x8
     field public static final int HAL_SERVICE_SIM = 5; // 0x5
     field public static final int HAL_SERVICE_VOICE = 6; // 0x6
     field public static final android.util.Pair HAL_VERSION_UNKNOWN;
@@ -3281,12 +3278,12 @@
   }
 
   public class MeasuredParagraph {
-    method @NonNull public static android.text.MeasuredParagraph buildForStaticLayoutTest(@NonNull android.text.TextPaint, @Nullable android.graphics.text.LineBreakConfig, @NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.text.TextDirectionHeuristic, int, boolean, @Nullable android.text.MeasuredParagraph.StyleRunCallback);
+    method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public static android.text.MeasuredParagraph buildForStaticLayoutTest(@NonNull android.text.TextPaint, @Nullable android.graphics.text.LineBreakConfig, @NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.text.TextDirectionHeuristic, int, boolean, @Nullable android.text.MeasuredParagraph.StyleRunCallback);
   }
 
-  public static interface MeasuredParagraph.StyleRunCallback {
-    method public void onAppendReplacementRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, @FloatRange(from=0) @Px float);
-    method public void onAppendStyleRun(@NonNull android.graphics.Paint, @Nullable android.graphics.text.LineBreakConfig, @IntRange(from=0) int, boolean);
+  @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static interface MeasuredParagraph.StyleRunCallback {
+    method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public void onAppendReplacementRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, @FloatRange(from=0) @Px float);
+    method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public void onAppendStyleRun(@NonNull android.graphics.Paint, @Nullable android.graphics.text.LineBreakConfig, @IntRange(from=0) int, boolean);
   }
 
   public static final class Selection.MemoryTextWatcher implements android.text.TextWatcher {
@@ -3408,7 +3405,7 @@
 
   public final class Choreographer {
     method public static long getFrameDelay();
-    method public long getFrameTimeNanos();
+    method @FlaggedApi("android.view.flags.expected_presentation_time_api") public long getFrameTimeNanos();
     method public void postCallback(int, Runnable, Object);
     method public void postCallbackDelayed(int, Runnable, Object, long);
     method public void removeCallbacks(int, Runnable, Object);
@@ -3569,8 +3566,8 @@
     method public default void holdLock(android.os.IBinder, int);
     method public default boolean isGlobalKey(int);
     method public default boolean isTaskSnapshotSupported();
-    method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithMirror(int, @NonNull android.view.Window);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithSc(int, @NonNull android.view.SurfaceControl);
+    method @FlaggedApi("REPLACE_CONTENT_WITH_MIRROR") @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithMirror(int, @NonNull android.view.Window);
+    method @FlaggedApi("REPLACE_CONTENT_WITH_MIRROR") @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithSc(int, @NonNull android.view.SurfaceControl);
     method public default void setDisplayImePolicy(int, int);
     method public default void setShouldShowSystemDecors(int, boolean);
     method public default void setShouldShowWithInsecureKeyguard(int, boolean);
@@ -3627,7 +3624,7 @@
 package android.view.animation {
 
   public class AnimationUtils {
-    method public static void lockAnimationClock(long, long);
+    method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static void lockAnimationClock(long, long);
     method public static void unlockAnimationClock();
   }
 
@@ -3947,7 +3944,9 @@
 package android.window {
 
   public final class BackNavigationInfo implements android.os.Parcelable {
+    method public int describeContents();
     method @NonNull public static String typeToString(int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.window.BackNavigationInfo> CREATOR;
     field public static final String KEY_TRIGGER_BACK = "TriggerBack";
     field public static final int TYPE_CALLBACK = 4; // 0x4
@@ -4008,11 +4007,13 @@
   }
 
   public final class TaskFragmentCreationParams implements android.os.Parcelable {
+    method public int describeContents();
     method @NonNull public android.os.IBinder getFragmentToken();
     method @NonNull public android.graphics.Rect getInitialRelativeBounds();
     method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizer();
     method @NonNull public android.os.IBinder getOwnerToken();
     method public int getWindowingMode();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentCreationParams> CREATOR;
   }
 
@@ -4024,6 +4025,7 @@
   }
 
   public final class TaskFragmentInfo implements android.os.Parcelable {
+    method public int describeContents();
     method public boolean equalsForTaskFragmentOrganizer(@Nullable android.window.TaskFragmentInfo);
     method @NonNull public java.util.List<android.os.IBinder> getActivities();
     method @NonNull public java.util.List<android.os.IBinder> getActivitiesRequestedInTaskFragment();
@@ -4037,6 +4039,7 @@
     method public boolean isEmpty();
     method public boolean isTaskClearedForReuse();
     method public boolean isVisible();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentInfo> CREATOR;
   }
 
@@ -4059,6 +4062,8 @@
   }
 
   public final class TaskFragmentOrganizerToken implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentOrganizerToken> CREATOR;
   }
 
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 107be8b..93e39d5 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -191,8 +191,14 @@
     New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_DEFAULT
 UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_OFF:
     New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_OFF
+UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_SYSTEM_DEFAULT:
+    New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_SYSTEM_DEFAULT
+UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_UNSPECIFIED:
+    New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_UNSPECIFIED
 UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_URI:
     New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_URI
+UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_APPLICATION_DEFAULT:
+    New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_APPLICATION_DEFAULT
 UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_APPLICATION_PROVIDED:
     New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_APPLICATION_PROVIDED
 UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_AUDIO_CHANNEL:
@@ -203,6 +209,10 @@
     New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_HAPTIC_GENERATOR
 UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_OFF:
     New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_OFF
+UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_SYSTEM_DEFAULT:
+    New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_SYSTEM_DEFAULT
+UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_UNSPECIFIED:
+    New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_UNSPECIFIED
 UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_URI:
     New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_URI
 UnflaggedApi: android.media.RingtoneSelection#fromUri(android.net.Uri, int):
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 13a1bd6..0293f66 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -23,11 +23,6 @@
     visibility: ["//frameworks/base"],
 }
 
-filegroup {
-    name: "IKeyAttestationApplicationIdProvider.aidl",
-    srcs: ["android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl"],
-}
-
 aidl_library {
     name: "IDropBoxManagerService_aidl",
     srcs: [
@@ -431,6 +426,16 @@
     },
 }
 
+aidl_interface {
+    name: "android.companion.virtual.virtualdevice_aidl",
+    unstable: true,
+    host_supported: true,
+    srcs: [
+        "android/companion/virtualnative/IVirtualDeviceManagerNative.aidl",
+    ],
+    local_include_dir: ".",
+}
+
 filegroup {
     name: "frameworks-base-java-overview",
     srcs: ["overview.html"],
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 3bf2cca..f68681b 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4288,7 +4288,7 @@
     }
 
     /**
-     * Start monitoring changes to the imoportance of uids running in the system.
+     * Start monitoring changes to the importance of uids running in the system.
      * @param listener The listener callback that will receive change reports.
      * @param importanceCutpoint The level of importance in which the caller is interested
      * in differences.  For example, if {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 895dde7..f2c0051 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -2073,9 +2073,7 @@
      * Allow a {@link PendingIntent} to use the privilege of its creator to start background
      * activities.
      *
-     * @param mode the {@link android.app.ComponentOptions.BackgroundActivityStartMode} being set
-     * @throws IllegalArgumentException is the value is not a valid
-     * {@link android.app.ComponentOptions.BackgroundActivityStartMode}
+     * @param mode the mode being set
      */
     @NonNull
     public ActivityOptions setPendingIntentCreatorBackgroundActivityStartMode(
@@ -2088,7 +2086,7 @@
      * Returns the mode to start background activities granted by the creator of the
      * {@link PendingIntent}.
      *
-     * @return the {@link android.app.ComponentOptions.BackgroundActivityStartMode} currently set
+     * @return the mode currently set
      */
     public @BackgroundActivityStartMode int getPendingIntentCreatorBackgroundActivityStartMode() {
         return mPendingIntentCreatorBackgroundActivityStartMode;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 9a90df9..e12181a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -101,6 +101,7 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.content.res.ResourcesImpl;
 import android.content.res.loader.ResourcesLoader;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteDebug;
@@ -297,6 +298,7 @@
     public static final boolean DEBUG_MEMORY_TRIM = false;
     private static final boolean DEBUG_PROVIDER = false;
     public static final boolean DEBUG_ORDER = false;
+    private static final boolean DEBUG_APP_INFO = true;
     private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
     /**
      * The delay to release the provider when it has no more references. It reduces the number of
@@ -6473,10 +6475,35 @@
             resApk.updateApplicationInfo(ai, oldPaths);
         }
 
+        ResourcesImpl beforeImpl = getApplication().getResources().getImpl();
+
         synchronized (mResourcesManager) {
             // Update all affected Resources objects to use new ResourcesImpl
             mResourcesManager.applyAllPendingAppInfoUpdates();
         }
+
+        ResourcesImpl afterImpl = getApplication().getResources().getImpl();
+
+        if ((beforeImpl != afterImpl) && !Arrays.equals(beforeImpl.getAssets().getApkAssets(),
+                afterImpl.getAssets().getApkAssets())) {
+            List<String> beforeAssets = Arrays.asList(beforeImpl.getAssets().getApkPaths());
+            List<String> afterAssets = Arrays.asList(afterImpl.getAssets().getApkPaths());
+
+            List<String> onlyBefore = new ArrayList<>(beforeAssets);
+            onlyBefore.removeAll(afterAssets);
+            List<String> onlyAfter = new ArrayList<>(afterAssets);
+            onlyAfter.removeAll(beforeAssets);
+
+            Slog.i(TAG, "ApplicationInfo updating for " + ai.packageName + ", new timestamp: "
+                    + ai.createTimestamp + "\nassets removed: " + onlyBefore + "\nassets added: "
+                    + onlyAfter);
+
+            if (DEBUG_APP_INFO) {
+                Slog.v(TAG, "ApplicationInfo updating for " + ai.packageName
+                        + ", assets before change: " + beforeAssets + "\n assets after change: "
+                        + afterAssets);
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index ca10d14..ecbc9b1 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1478,7 +1478,8 @@
             AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED;
 
     /**
-     * Allows the assistant app to receive the PCC-validated hotword and be voice-triggered.
+     * Allows the assistant app to be voice-triggered by detected hotwords from a trusted detection
+     * service.
      *
      * @hide
      */
@@ -1486,13 +1487,13 @@
             AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
 
     /**
-     * Allows the assistant app to get the training data from the PCC sandbox 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_SANDBOX_TRAINING_DATA =
-            AppProtoEnums.APP_OP_RECEIVE_SANDBOX_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)
@@ -1640,7 +1641,7 @@
             OPSTR_CAMERA_SANDBOXED,
             OPSTR_RECORD_AUDIO_SANDBOXED,
             OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
-            OPSTR_RECEIVE_SANDBOX_TRAINING_DATA
+            OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA
     })
     public @interface AppOpString {}
 
@@ -2252,22 +2253,22 @@
     public static final String OPSTR_USE_FULL_SCREEN_INTENT = "android:use_full_screen_intent";
 
     /**
-     * Allows the assistant app to receive the PCC-validated hotword and be voice-triggered.
+     * Allows the assistant app to be voice-triggered by detected hotwords from a trusted detection
+     * service.
      *
      * @hide
      */
-    @SystemApi
     public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO =
             "android:receive_sandbox_trigger_audio";
 
     /**
-     * Allows the assistant app to get the training data from the PCC sandbox 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_SANDBOX_TRAINING_DATA =
-            "android:receive_sandbox_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;
@@ -2379,7 +2380,9 @@
             OP_RUN_USER_INITIATED_JOBS,
             OP_FOREGROUND_SERVICE_SPECIAL_USE,
             OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
-            OP_USE_FULL_SCREEN_INTENT
+            OP_USE_FULL_SCREEN_INTENT,
+            OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
+            OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA
     };
 
     static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2810,10 +2813,13 @@
         new AppOpInfo.Builder(OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
                 OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
                 "RECEIVE_SANDBOX_TRIGGER_AUDIO")
-                .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
-        new AppOpInfo.Builder(OP_RECEIVE_SANDBOX_TRAINING_DATA,
-                OPSTR_RECEIVE_SANDBOX_TRAINING_DATA,
-                "RECEIVE_SANDBOX_TRAINING_DATA").build()
+                .setPermission(Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO)
+                .setDefaultMode(AppOpsManager.MODE_DEFAULT).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/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index e5a73be..21ed098 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2954,6 +2954,17 @@
         }
     }
 
+    @Override
+    public boolean isPackageStopped(@NonNull String packageName) throws NameNotFoundException {
+        try {
+            return mPM.isPackageStoppedForUser(packageName, getUserId());
+        } catch (IllegalArgumentException ie) {
+            throw new NameNotFoundException(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** @hide */
     @Override
     public void setApplicationCategoryHint(String packageName, int categoryHint) {
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 9549ebf..41b4004 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -843,8 +843,7 @@
      * considered to be in the same delivery group as this iff it has the same {@code namespace}
      * and {@code key}.
      *
-     * <p> If neither matching key using this API nor matching filter using
-     * {@link #setDeliveryGroupMatchingFilter(IntentFilter)} is specified, then by default
+     * <p> If not matching key using this API then by default
      * {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
      */
     @NonNull
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 8fea03b..ebf183f 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -50,7 +50,6 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.security.net.config.NetworkSecurityConfigProvider;
-import android.sysprop.VndkProperties;
 import android.text.TextUtils;
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
@@ -901,14 +900,10 @@
         }
 
         // Similar to vendor apks, we should add /product/lib for apks from product partition
-        // when product apps are marked as unbundled. We cannot use the same way from vendor
-        // to check if lib path exists because there is possibility that /product/lib would not
-        // exist from legacy device while product apks are bundled. To make this clear, we use
-        // "ro.product.vndk.version" property. If the property is defined, we regard all product
-        // apks as unbundled.
+        // when product apps are marked as unbundled. Product is separated as long as the
+        // partition exists, so it can be handled with same approach from the vendor partition.
         if (mApplicationInfo.getCodePath() != null
-                && mApplicationInfo.isProduct()
-                && VndkProperties.product_vndk_version().isPresent()) {
+                && mApplicationInfo.isProduct()) {
             isBundledApp = false;
         }
 
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index dd7db23..94e1292 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1514,13 +1514,13 @@
 
     /**
      * {@link #extras} key: the color used as a hint for the Answer action button of a
-     * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}.
+     * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}.
      */
     public static final String EXTRA_ANSWER_COLOR = "android.answerColor";
 
     /**
      * {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a
-     * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}.
+     * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}.
      */
     public static final String EXTRA_DECLINE_COLOR = "android.declineColor";
 
@@ -1704,7 +1704,7 @@
         private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
 
         /**
-         * {@link }: No semantic action defined.
+         * No semantic action defined.
          */
         public static final int SEMANTIC_ACTION_NONE = 0;
 
@@ -7923,7 +7923,7 @@
      * (via {@link Notification.Builder#setShortcutId(String)}) are displayed in a dedicated
      * conversation section in the shade above non-conversation alerting and silence notifications.
      * To be a valid conversation shortcut, the shortcut must be a
-     * {@link ShortcutInfo#setLongLived()} dynamic or cached sharing shortcut.
+     * {@link ShortcutInfo.Builder#setLongLived(boolean)} dynamic or cached sharing shortcut.
      *
      * <p>
      * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior.
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 7ee1332..15d692a 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -272,6 +272,10 @@
     private boolean mDemoted = false;
     private boolean mImportantConvo = false;
     private long mDeletedTime = DEFAULT_DELETION_TIME_MS;
+    /** Do not (de)serialize this value: it only affects logic in system_server and that logic
+     * is reset on each boot {@link NotificationAttentionHelper#buzzBeepBlinkLocked}.
+     */
+    private long mLastNotificationUpdateTimeMs = 0;
 
     /**
      * Creates a notification channel.
@@ -932,6 +936,23 @@
     }
 
     /**
+     * Returns the time of the notification post or last update for this channel.
+     * @return time of post / last update
+     * @hide
+     */
+    public long getLastNotificationUpdateTimeMs() {
+        return mLastNotificationUpdateTimeMs;
+    }
+
+    /**
+     * Sets the time of the notification post or last update for this channel.
+     * @hide
+     */
+    public void setLastNotificationUpdateTimeMs(long updateTimeMs) {
+        mLastNotificationUpdateTimeMs = updateTimeMs;
+    }
+
+    /**
      * @hide
      */
     public void populateFromXmlForRestore(XmlPullParser parser, boolean pkgInstalled,
@@ -1408,7 +1429,8 @@
                 + ", mParent=" + mParentId
                 + ", mConversationId=" + mConversationId
                 + ", mDemoted=" + mDemoted
-                + ", mImportantConvo=" + mImportantConvo;
+                + ", mImportantConvo=" + mImportantConvo
+                + ", mLastNotificationUpdateTimeMs=" + mLastNotificationUpdateTimeMs;
     }
 
     /** @hide */
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 79b68c1..b8bea9d 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -25,8 +25,12 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.UserHandleAware;
 import android.annotation.WorkerThread;
 import android.app.Notification.Builder;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -1659,23 +1663,42 @@
     }
 
     /**
+     * For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, the
+     * {@code setNotificationListenerAccessGranted} method will use the user contained within the
+     * context.
+     * For apps targeting an SDK version <em>below</em> this, the user of the calling process will
+     * be used (Process.myUserHandle()).
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public static final long SET_LISTENER_ACCESS_GRANTED_IS_USER_AWARE = 302563478L;
+
+    /**
      * Grants/revokes Notification Listener access to the given component for current user.
      * To grant access for a particular user, obtain this service by using the {@link Context}
      * provided by {@link Context#createPackageContextAsUser}
      *
      * @param listener Name of component to grant/revoke access
-     * @param granted Grant/revoke access
-     * @param userSet Whether the action was triggered explicitly by user
+     * @param granted  Grant/revoke access
+     * @param userSet  Whether the action was triggered explicitly by user
      * @hide
      */
     @SystemApi
     @TestApi
+    @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
     @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS)
     public void setNotificationListenerAccessGranted(
             @NonNull ComponentName listener, boolean granted, boolean userSet) {
         INotificationManager service = getService();
         try {
-            service.setNotificationListenerAccessGranted(listener, granted, userSet);
+            if (CompatChanges.isChangeEnabled(SET_LISTENER_ACCESS_GRANTED_IS_USER_AWARE)) {
+                service.setNotificationListenerAccessGrantedForUser(listener, mContext.getUserId(),
+                        granted, userSet);
+            } else {
+                service.setNotificationListenerAccessGranted(listener, granted, userSet);
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index 456c6af..7c69d37 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -1172,6 +1172,12 @@
             public static final String WORK_CATEGORY_HEADER = PREFIX + "WORK_CATEGORY_HEADER";
 
             /**
+             * Header for items under the private user
+             */
+            public static final String PRIVATE_CATEGORY_HEADER =
+                    PREFIX + "PRIVATE_CATEGORY_HEADER";
+
+            /**
              * Header for items under the personal user
              */
             public static final String PERSONAL_CATEGORY_HEADER =
@@ -1208,6 +1214,12 @@
             public static final String AUTO_SYNC_WORK_DATA = PREFIX + "AUTO_SYNC_WORK_DATA";
 
             /**
+             * Text for toggle to enable auto-sycing private data
+             */
+            public static final String AUTO_SYNC_PRIVATE_DATA = PREFIX
+                    + "AUTO_SYNC_PRIVATE_DATA";
+
+            /**
              * Summary for "More security settings" section when a work profile is on the device.
              */
             public static final String MORE_SECURITY_SETTINGS_WORK_PROFILE_SUMMARY = PREFIX
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/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index afe87de..d1f9067 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -7,3 +7,9 @@
     bug: "296061232"
 }
 
+flag {
+    name: "report_usage_stats_permission"
+    namespace: "backstage_power"
+    description: "Feature flag for the new REPORT_USAGE_STATS permission."
+    bug: "296056771"
+}
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 ce883cd..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 {
@@ -113,14 +110,13 @@
      * <p class="note">This identifier may not be unique across virtual devices, in case there are
      * more than one virtual devices corresponding to the same physical device.
      */
+    @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
     public @Nullable String getPersistentDeviceId() {
         return mPersistentId;
     }
 
     /**
      * 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/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 39800f7..2569366 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -476,6 +476,7 @@
         /**
          * Returns the persistent ID of this virtual device.
          */
+        @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
         public @Nullable String getPersistentDeviceId() {
             return mVirtualDeviceInternal.getPersistentDeviceId();
         }
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index d0e13cd..cf274f5 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -8,6 +8,14 @@
 }
 
 flag {
+  name: "enable_native_vdm"
+  namespace: "virtual_devices"
+  description: "Enable native VDM service"
+  bug: "303535376"
+  is_fixed_read_only: true
+}
+
+flag {
   name: "dynamic_policy"
   namespace: "virtual_devices"
   description: "Enable dynamic policy API"
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/companion/virtualnative/IVirtualDeviceManagerNative.aidl b/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl
new file mode 100644
index 0000000..9f09d04
--- /dev/null
+++ b/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtualnative;
+
+/**
+ * Parallel implementation of certain VirtualDeviceManager APIs that need to be exposed to native
+ * code.
+ *
+ * <p>These APIs are a parallel definition to the APIs in VirtualDeviceManager and/or
+ * VirtualDeviceManagerInternal, so they can technically diverge. However, it's good practice to
+ * keep these APIs in sync with each other.</p>
+ *
+ * <p>Even though the name implies otherwise, the implementation is actually in Java. The 'native'
+ * suffix comes from the intended usage - native framework backends that need to communicate with
+ * VDM for some reason.</p>
+ *
+ * <p>Because these APIs are exposed to native code that runs in the app process, they may be
+ * accessed by apps directly, even though they're hidden. Care should be taken to avoid exposing
+ * sensitive data or potential security holes.</p>
+ *
+ * @hide
+ */
+interface IVirtualDeviceManagerNative {
+    /**
+     * Counterpart to VirtualDeviceParams#DevicePolicy.
+     */
+    const int DEVICE_POLICY_DEFAULT = 0;
+    const int DEVICE_POLICY_CUSTOM = 1;
+
+    /**
+     * Counterpart to VirtualDeviceParams#PolicyType.
+     */
+    const int POLICY_TYPE_SENSORS = 0;
+    const int POLICY_TYPE_AUDIO = 1;
+    const int POLICY_TYPE_RECENTS = 2;
+    const int POLICY_TYPE_ACTIVITY = 3;
+
+    /**
+     * Returns the IDs for all VirtualDevices where an app with the given is running.
+     *
+     * Note that this returns only VirtualDevice IDs: if the app is not running on any virtual
+     * device, then an an empty array is returned. This does not include information about whether
+     * the app is running on the default device or not.
+     */
+    int[] getDeviceIdsForUid(int uid);
+
+    /**
+     * Returns the device policy for the given virtual device and policy type.
+     */
+    int getDevicePolicy(int deviceId, int policyType);
+}
\ No newline at end of file
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index bfc1eec..20eae9a 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -730,7 +730,7 @@
         /**
          * The next app to receive the permission protected data.
          *
-         * @deprecated Use {@link setNextAttributionSource} instead.
+         * @deprecated Use {@link #setNextAttributionSource} instead.
          */
         @Deprecated
         public @NonNull Builder setNext(@Nullable AttributionSource value) {
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/Context.java b/core/java/android/content/Context.java
index b6a98a5..884351b 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4357,7 +4357,6 @@
      * @see android.telephony.CarrierConfigManager
      * @see #EUICC_SERVICE
      * @see android.telephony.euicc.EuiccManager
-     * @see android.telephony.MmsManager
      * @see #INPUT_METHOD_SERVICE
      * @see android.view.inputmethod.InputMethodManager
      * @see #UI_MODE_SERVICE
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 44a9acd..1eb2cd1 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2790,6 +2790,20 @@
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED";
+
+    /**
+     * Broadcast Action: An application package that was previously in the stopped state has been
+     * started and is no longer considered stopped.
+     * <ul>
+     * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+     * </ul>
+     *
+     * <p class="note">This is a protected intent that can only be sent by the system.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    @FlaggedApi(android.content.pm.Flags.FLAG_STAY_STOPPED)
+    public static final String ACTION_PACKAGE_UNSTOPPED = "android.intent.action.PACKAGE_UNSTOPPED";
+
     /**
      * Broadcast Action: Sent to the system rollback manager when a package
      * needs to have rollback enabled.
@@ -4166,7 +4180,7 @@
      * new state of quiet mode. This is only sent to registered receivers, not manifest receivers.
      *
      * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_AVAILABLE} but functions as a
-     * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+     * generic broadcast for all profile users.
      */
     @FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)
     public static final String ACTION_PROFILE_AVAILABLE =
@@ -4180,7 +4194,7 @@
      * new state of quiet mode. This is only sent to registered receivers, not manifest receivers.
      *
      * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_UNAVAILABLE} but functions as
-     * a generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+     * a generic broadcast for all profile users.
      */
     @FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)
     public static final String ACTION_PROFILE_UNAVAILABLE =
@@ -4208,7 +4222,7 @@
      * that was removed.
      *
      * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_REMOVED} but functions as a
-     * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+     * generic broadcast for all profile users.
      * It is sent in addition to the {@link #ACTION_MANAGED_PROFILE_REMOVED} broadcast when a
      * managed user is removed.
      *
@@ -4228,7 +4242,7 @@
      * that was added.
      *
      * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_ADDED} but functions as a
-     * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+     * generic broadcast for all profile users.
      * It is sent in addition to the {@link #ACTION_MANAGED_PROFILE_ADDED} broadcast when a
      * managed user is added.
      *
diff --git a/core/java/android/content/om/OverlayIdentifier.java b/core/java/android/content/om/OverlayIdentifier.java
index f256372..30875aa 100644
--- a/core/java/android/content/om/OverlayIdentifier.java
+++ b/core/java/android/content/om/OverlayIdentifier.java
@@ -39,7 +39,6 @@
  * -->
  *
  * @see OverlayInfo#getOverlayIdentifier()
- * @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)
  */
 @DataClass(genConstructor = false, genBuilder = false, genHiddenBuilder = false,
         genEqualsHashCode = true, genToString = false)
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index ff1c088..2e89856 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -385,7 +385,6 @@
      * <p>The return value of this function can be used to unregister the related overlay.
      *
      * @return an identifier representing the current overlay.
-     * @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)
      */
     @Override
     @NonNull
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 0fcc72a1..ed965b3 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -50,7 +50,7 @@
  *   <li>register overlays
  *   <li>unregister overlays
  *   <li>execute multiple operations in one commitment by calling {@link
- *       OverlayManagerTransaction#commit()}
+ *       #commit(OverlayManagerTransaction)}
  * </ul>
  *
  * @see OverlayManagerTransaction
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 036a4eb..aefa55f 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1185,8 +1185,8 @@
      * <p>For {@link android.view.View#getWindowVisibleDisplayFrame} and
      * {@link android.view.View}#getWindowDisplayFrame this sandboxing is happening indirectly
      * through
-     * {@link android.view.ViewRootImpl}#getWindowVisibleDisplayFrame,
-     * {@link android.view.ViewRootImpl}#getDisplayFrame respectively.
+     * {@code android.view.ViewRootImpl#getWindowVisibleDisplayFrame},
+     * {@code android.view.ViewRootImpl#getDisplayFrame} respectively.
      *
      * <p>Some applications assume that they occupy the whole screen and therefore use the display
      * coordinates in their calculations as if an activity is  positioned in the top-left corner of
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index aca88d6..9926415 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -308,6 +308,8 @@
 
     boolean isPackageQuarantinedForUser(String packageName, int userId);
 
+    boolean isPackageStoppedForUser(String packageName, int userId);
+
     Bundle getSuspendedPackageAppExtras(String packageName, int userId);
 
     /**
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 3a9e9bf..d837aae 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2730,8 +2730,8 @@
          * Sets the state of permissions for the package at installation.
          * <p/>
          * Granting any runtime permissions require the
-         * {@link android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS
-         * INSTALL_GRANT_RUNTIME_PERMISSIONS} permission to be held by the caller. Revoking runtime
+         * {@code android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS}
+         * permission to be held by the caller. Revoking runtime
          * permissions is not allowed, even during app update sessions.
          * <p/>
          * Holders without the permission are allowed to change the following special permissions:
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8fbe50c3..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;
 
@@ -9878,6 +9888,18 @@
     }
 
     /**
+     * Query if an app is currently stopped.
+     *
+     * @return {@code true} if the given package is stopped, {@code false} otherwise
+     * @throws NameNotFoundException if the package could not be found.
+     * @see ApplicationInfo#FLAG_STOPPED
+     */
+    @FlaggedApi(android.content.pm.Flags.FLAG_STAY_STOPPED)
+    public boolean isPackageStopped(@NonNull String packageName) throws NameNotFoundException {
+        throw new UnsupportedOperationException("isPackageStopped not implemented");
+    }
+
+    /**
      * Query if an app is currently quarantined.
      *
      * @return {@code true} if the given package is quarantined, {@code false} otherwise
@@ -9888,7 +9910,6 @@
     public boolean isPackageQuarantined(@NonNull String packageName) throws NameNotFoundException {
         throw new UnsupportedOperationException("isPackageQuarantined not implemented");
     }
-
     /**
      * Provide a hint of what the {@link ApplicationInfo#category} value should
      * be for the given package.
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 65f56f6..9869179 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -132,11 +132,6 @@
      * the {@link android.R.attr#foregroundServiceType} attribute.
      * Data(photo, file, account) upload/download, backup/restore, import/export, fetch,
      * transfer over network between device and cloud.
-     *
-     * <p class="note">
-     * Use the {@link android.app.job.JobInfo.Builder#setDataTransfer} API for data transfers
-     * that can be deferred until conditions are ideal for the app or device.
-     * </p>
      */
     @RequiresPermission(
             value = Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC,
diff --git a/core/java/android/content/pm/Signature.aidl b/core/java/android/content/pm/Signature.aidl
deleted file mode 100644
index 36c127a..0000000
--- a/core/java/android/content/pm/Signature.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/* //device/java/android/android/view/WindowManager.aidl
-**
-** Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-package android.content.pm;
-
-/* For the key attestation application id provider service we needed a native implementation
- * of the Signature parcelable because the service is used by the native keystore.
- * The native implementation is now located at
- * system/security/keystore/Signature.cpp
- * and
- * system/security/keystore/include/keystore/Signature.h.
- * and can be used by linking against libkeystore_binder.
- *
- * This is not the best arrangement. If you, dear reader, happen to implement native implementations
- * for the package manager's parcelables, consider moving Signature.cpp/.h to your library and
- * adjust keystore's dependencies accordingly. Thank you.
- */
-parcelable Signature cpp_header "keystore/Signature.h";
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 96af2b6..884d463 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -49,6 +49,7 @@
     private static final String ATTR_SHOW_IN_LAUNCHER = "showInLauncher";
     private static final String ATTR_START_WITH_PARENT = "startWithParent";
     private static final String ATTR_SHOW_IN_SETTINGS = "showInSettings";
+    private static final String ATTR_HIDE_IN_SETTINGS_IN_QUIET_MODE = "hideInSettingsInQuietMode";
     private static final String ATTR_INHERIT_DEVICE_POLICY = "inheritDevicePolicy";
     private static final String ATTR_USE_PARENTS_CONTACTS = "useParentsContacts";
     private static final String ATTR_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA =
@@ -78,6 +79,7 @@
             INDEX_CREDENTIAL_SHAREABLE_WITH_PARENT,
             INDEX_DELETE_APP_WITH_PARENT,
             INDEX_ALWAYS_VISIBLE,
+            INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE,
     })
     @Retention(RetentionPolicy.SOURCE)
     private @interface PropertyIndex {
@@ -94,6 +96,7 @@
     private static final int INDEX_CREDENTIAL_SHAREABLE_WITH_PARENT = 9;
     private static final int INDEX_DELETE_APP_WITH_PARENT = 10;
     private static final int INDEX_ALWAYS_VISIBLE = 11;
+    private static final int INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE = 12;
     /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
     private long mPropertiesPresent = 0;
 
@@ -324,6 +327,7 @@
         if (hasManagePermission) {
             // Add items that require MANAGE_USERS or stronger.
             setShowInSettings(orig.getShowInSettings());
+            setHideInSettingsInQuietMode(orig.getHideInSettingsInQuietMode());
             setUseParentsContacts(orig.getUseParentsContacts());
         }
         if (hasQueryOrManagePermission) {
@@ -409,6 +413,42 @@
     private @ShowInSettings int mShowInSettings;
 
     /**
+     * Returns whether a user should be shown in the Settings app depending on the quiet mode.
+     * This is generally inapplicable for non-profile users.
+     *
+     * <p> {@link #getShowInSettings()} returns whether / how a user should be shown in Settings.
+     * However, if this behaviour should be changed based on the quiet mode of the user, then this
+     * property can be used. If the property is not set then the user is shown in the Settings app
+     * irrespective of whether the user is in quiet mode or not. If the property is set, then the
+     * user is shown in the Settings app only if the user is not in the quiet mode. Please note that
+     * this property takes effect only if {@link #getShowInSettings()} does not return
+     * {@link #SHOW_IN_SETTINGS_NO}.
+     *
+     * <p> The caller must have {@link android.Manifest.permission#MANAGE_USERS} to query this
+     * property.
+     *
+     * @return true if a profile should be shown in the Settings only when the user is not in the
+     * quiet mode.
+     *
+     * See also {@link #getShowInSettings()}, {@link #setShowInSettings(int)},
+     * {@link ShowInSettings}
+     *
+     * @hide
+     */
+    public boolean getHideInSettingsInQuietMode() {
+        if (isPresent(INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE)) return mHideInSettingsInQuietMode;
+        if (mDefaultProperties != null) return mDefaultProperties.mHideInSettingsInQuietMode;
+        throw new SecurityException(
+                "You don't have permission to query HideInSettingsInQuietMode");
+    }
+    /** @hide */
+    public void setHideInSettingsInQuietMode(boolean hideInSettingsInQuietMode) {
+        this.mHideInSettingsInQuietMode = hideInSettingsInQuietMode;
+        setPresent(INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE);
+    }
+    private boolean mHideInSettingsInQuietMode;
+
+    /**
      * Returns whether a profile should be started when its parent starts (unless in quiet mode).
      * This only applies for users that have parents (i.e. for profiles).
      * @hide
@@ -724,6 +764,9 @@
                 case ATTR_SHOW_IN_SETTINGS:
                     setShowInSettings(parser.getAttributeInt(i));
                     break;
+                case ATTR_HIDE_IN_SETTINGS_IN_QUIET_MODE:
+                    setHideInSettingsInQuietMode(parser.getAttributeBoolean(i));
+                    break;
                 case ATTR_INHERIT_DEVICE_POLICY:
                     setInheritDevicePolicy(parser.getAttributeInt(i));
                     break;
@@ -777,6 +820,10 @@
         if (isPresent(INDEX_SHOW_IN_SETTINGS)) {
             serializer.attributeInt(null, ATTR_SHOW_IN_SETTINGS, mShowInSettings);
         }
+        if (isPresent(INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE)) {
+            serializer.attributeBoolean(null, ATTR_HIDE_IN_SETTINGS_IN_QUIET_MODE,
+                    mHideInSettingsInQuietMode);
+        }
         if (isPresent(INDEX_INHERIT_DEVICE_POLICY)) {
             serializer.attributeInt(null, ATTR_INHERIT_DEVICE_POLICY,
                     mInheritDevicePolicy);
@@ -823,6 +870,7 @@
         dest.writeInt(mShowInLauncher);
         dest.writeBoolean(mStartWithParent);
         dest.writeInt(mShowInSettings);
+        dest.writeBoolean(mHideInSettingsInQuietMode);
         dest.writeInt(mInheritDevicePolicy);
         dest.writeBoolean(mUseParentsContacts);
         dest.writeBoolean(mUpdateCrossProfileIntentFiltersOnOTA);
@@ -845,6 +893,7 @@
         mShowInLauncher = source.readInt();
         mStartWithParent = source.readBoolean();
         mShowInSettings = source.readInt();
+        mHideInSettingsInQuietMode = source.readBoolean();
         mInheritDevicePolicy = source.readInt();
         mUseParentsContacts = source.readBoolean();
         mUpdateCrossProfileIntentFiltersOnOTA = source.readBoolean();
@@ -881,6 +930,7 @@
         private @ShowInLauncher int mShowInLauncher = SHOW_IN_LAUNCHER_WITH_PARENT;
         private boolean mStartWithParent = false;
         private @ShowInSettings int mShowInSettings = SHOW_IN_SETTINGS_WITH_PARENT;
+        private boolean mHideInSettingsInQuietMode = false;
         private @InheritDevicePolicy int mInheritDevicePolicy = INHERIT_DEVICE_POLICY_NO;
         private boolean mUseParentsContacts = false;
         private boolean mUpdateCrossProfileIntentFiltersOnOTA = false;
@@ -910,6 +960,12 @@
             return this;
         }
 
+        /** Sets the value for {@link #mHideInSettingsInQuietMode} */
+        public Builder setHideInSettingsInQuietMode(boolean hideInSettingsInQuietMode) {
+            mHideInSettingsInQuietMode = hideInSettingsInQuietMode;
+            return this;
+        }
+
         /** Sets the value for {@link #mInheritDevicePolicy}*/
         public Builder setInheritDevicePolicy(
                 @InheritDevicePolicy int inheritRestrictionsDevicePolicy) {
@@ -972,6 +1028,7 @@
                     mShowInLauncher,
                     mStartWithParent,
                     mShowInSettings,
+                    mHideInSettingsInQuietMode,
                     mInheritDevicePolicy,
                     mUseParentsContacts,
                     mUpdateCrossProfileIntentFiltersOnOTA,
@@ -989,6 +1046,7 @@
             @ShowInLauncher int showInLauncher,
             boolean startWithParent,
             @ShowInSettings int showInSettings,
+            boolean hideInSettingsInQuietMode,
             @InheritDevicePolicy int inheritDevicePolicy,
             boolean useParentsContacts, boolean updateCrossProfileIntentFiltersOnOTA,
             @CrossProfileIntentFilterAccessControlLevel int crossProfileIntentFilterAccessControl,
@@ -1001,6 +1059,7 @@
         setShowInLauncher(showInLauncher);
         setStartWithParent(startWithParent);
         setShowInSettings(showInSettings);
+        setHideInSettingsInQuietMode(hideInSettingsInQuietMode);
         setInheritDevicePolicy(inheritDevicePolicy);
         setUseParentsContacts(useParentsContacts);
         setUpdateCrossProfileIntentFiltersOnOTA(updateCrossProfileIntentFiltersOnOTA);
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 89ca915..b2cc070 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -19,6 +19,7 @@
     namespace: "package_manager_service"
     description: "Feature flag to enable the prevent sdk-library be an application."
     bug: "295843617"
+    is_fixed_read_only: true
 }
 
 flag {
@@ -42,3 +43,10 @@
     description: "Feature flag to enable the feature to retrieve package info without installation."
     bug: "269149275"
 }
+
+flag {
+    name: "use_art_service_v2"
+    namespace: "package_manager_service"
+    description: "Feature flag to enable the features that rely on new ART Service APIs that are in the VIC version of the ART module."
+    bug: "304741685"
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index ea0f049..3ec239c 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -8,8 +8,23 @@
 }
 
 flag {
+    name: "save_global_and_guest_restrictions_on_system_user_xml_read_only"
+    namespace: "multiuser"
+    description: "Save guest and device policy global restrictions on the SYSTEM user's XML file. (Read only flag)"
+    bug: "301067944"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "bind_wallpaper_service_on_its_own_thread_during_a_user_switch"
     namespace: "multiuser"
     description: "Bind wallpaper service on its own thread instead of system_server's main handler during a user switch."
     bug: "302100344"
 }
+
+flag {
+    name: "support_communal_profile"
+    namespace: "multiuser"
+    description: "Framework support for communal profile."
+    bug: "285426179"
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java
index 1e60abb..7ada946 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java
@@ -231,7 +231,7 @@
     }
 
     /**
-     * Mapping of domain host to state, as defined by {@link DomainState}.
+     * Mapping of domain host to state, as defined by the {@code DOMAIN_STATE_*} constants
      */
     @DataClass.Generated.Member
     public @NonNull Map<String,Integer> getHostToStateMap() {
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index ed22284..1b37092 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -2210,8 +2210,7 @@
      *
      * <p>The best practices is to obtain metrics from
      * {@link WindowManager#getCurrentWindowMetrics()} for window bounds. The value obtained from
-     * this API may be wrong if {@link Context#getResources()} is from
-     * non-{@link android.annotation.UiContext}.
+     * this API may be wrong if {@link Context#getResources()} is not from a {@code UiContext}.
      * For example, use the {@link DisplayMetrics} obtained from {@link Application#getResources()}
      * to build {@link android.app.Activity} UI elements especially when the
      * {@link android.app.Activity} is in the multi-window mode or on the secondary {@link Display}.
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..38fbd72 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,23 @@
         return mSettingsSubtitle;
     }
 
+    /**
+     * Returns the settings activity.
+     *
+     * @hide
+     */
+    @Nullable
+    @TestApi
+    @FlaggedApi(Flags.FLAG_SETTINGS_ACTIVITY_ENABLED)
+    public CharSequence getSettingsActivity() {
+        // Add a manual check to make sure this returns null if
+        // the flag is not enabled.
+        if (!Flags.settingsActivityEnabled()) {
+            return null;
+        }
+        return mSettingsActivity;
+    }
+
     /** Returns the component name for the service. */
     @NonNull
     public ComponentName getComponentName() {
@@ -133,6 +151,7 @@
         dest.writeBoolean(mIsPrimary);
         TextUtils.writeToParcel(mOverrideLabel, dest, flags);
         TextUtils.writeToParcel(mSettingsSubtitle, dest, flags);
+        TextUtils.writeToParcel(mSettingsActivity, dest, flags);
     }
 
     @Override
@@ -161,6 +180,9 @@
                 + "settingsSubtitle="
                 + mSettingsSubtitle
                 + ", "
+                + "settingsActivity="
+                + mSettingsActivity
+                + ", "
                 + "capabilities="
                 + String.join(",", mCapabilities)
                 + "}";
@@ -174,6 +196,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 +219,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 +255,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/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index 0d0305b..9b819a7 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -5,4 +5,11 @@
     name: "settings_activity_enabled"
     description: "Enable the Credential Manager Settings Activity APIs"
     bug: "300014059"
-}
\ No newline at end of file
+}
+
+flag {
+    namespace: "credential_manager"
+    name: "instant_apps_enabled"
+    description: "Enables Credential Manager to work with Instant Apps"
+    bug: "302190269"
+}
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/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java
index 151f819..6ac1efb 100644
--- a/core/java/android/hardware/biometrics/CryptoObject.java
+++ b/core/java/android/hardware/biometrics/CryptoObject.java
@@ -114,8 +114,8 @@
     }
 
     /**
-     * Get {@link PresentationSession} object.
-     * @return {@link PresentationSession} object or null if this doesn't contain one.
+     * Get {@link KeyAgreement} object.
+     * @return {@link KeyAgreement} object or null if this doesn't contain one.
      */
     @FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT)
     public KeyAgreement getKeyAgreement() {
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/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index 99f3d15..53a9a75 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -59,7 +59,8 @@
 
     @IntDef(prefix = {"BRIGHTNESS_MAX_REASON_"}, value = {
             BRIGHTNESS_MAX_REASON_NONE,
-            BRIGHTNESS_MAX_REASON_THERMAL
+            BRIGHTNESS_MAX_REASON_THERMAL,
+            BRIGHTNESS_MAX_REASON_POWER_IC
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface BrightnessMaxReason {}
@@ -74,6 +75,11 @@
      */
     public static final int BRIGHTNESS_MAX_REASON_THERMAL = 1;
 
+    /**
+     * Maximum brightness is restricted due to power throttling.
+     */
+    public static final int BRIGHTNESS_MAX_REASON_POWER_IC = 2;
+
     /** Brightness */
     public final float brightness;
 
@@ -144,6 +150,8 @@
                 return "none";
             case BRIGHTNESS_MAX_REASON_THERMAL:
                 return "thermal";
+            case BRIGHTNESS_MAX_REASON_POWER_IC:
+                return "power IC";
         }
         return "invalid";
     }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 990ebc5..a4593be 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -20,6 +20,7 @@
 import static android.view.Display.HdrCapabilities.HdrType;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -27,7 +28,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -377,7 +377,7 @@
      * @see #createVirtualDisplay
      * @hide
      */
-    @SuppressLint("UnflaggedApi")
+    @FlaggedApi(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS)
     @SystemApi
     public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 << 7;
 
@@ -1819,6 +1819,12 @@
         String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data";
 
         /**
+         * Key for the power throttling data as a String formatted, from the display
+         * device config.
+         */
+        String KEY_POWER_THROTTLING_DATA = "power_throttling_data";
+
+        /**
          * Key for new power controller feature flag. If enabled new DisplayPowerController will
          * be used.
          * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index ff1a6ac..7b8419e 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1373,7 +1373,7 @@
     public interface InputDeviceListener {
         /**
          * Called whenever an input device has been added to the system.
-         * Use {@link InputManagerGlobal#getInputDevice} to get more information about the device.
+         * Use {@link #getInputDevice(int)} to get more information about the device.
          *
          * @param deviceId The id of the input device that was added.
          */
diff --git a/core/java/android/hardware/input/KeyboardLayout.java b/core/java/android/hardware/input/KeyboardLayout.java
index bbfed24..883f157 100644
--- a/core/java/android/hardware/input/KeyboardLayout.java
+++ b/core/java/android/hardware/input/KeyboardLayout.java
@@ -82,8 +82,8 @@
         DVORAK(4, LAYOUT_TYPE_DVORAK),
         COLEMAK(5, LAYOUT_TYPE_COLEMAK),
         WORKMAN(6, LAYOUT_TYPE_WORKMAN),
-        TURKISH_F(7, LAYOUT_TYPE_TURKISH_F),
-        TURKISH_Q(8, LAYOUT_TYPE_TURKISH_Q),
+        TURKISH_Q(7, LAYOUT_TYPE_TURKISH_Q),
+        TURKISH_F(8, LAYOUT_TYPE_TURKISH_F),
         EXTENDED(9, LAYOUT_TYPE_EXTENDED);
 
         private final int mValue;
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 582c5c0..4c2bbc1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -3231,7 +3231,7 @@
     }
 
     /**
-     * Called when the user tapped or clicked an {@link android.widget.Editor}.
+     * Called when the user tapped or clicked an editor.
      * This can be useful when IME makes a decision of showing Virtual keyboard based on what
      * {@link MotionEvent#getToolType(int)} was used to click the editor.
      * e.g. when toolType is {@link MotionEvent#TOOL_TYPE_STYLUS}, IME may choose to show a
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index a6d8caf..0c95c2e 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -79,4 +79,9 @@
     Map getTagIntentAppPreferenceForUser(int userId);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
     int setTagIntentAppPreferenceForUser(int userId, String pkg, boolean allow);
+
+    boolean isReaderOptionEnabled();
+    boolean isReaderOptionSupported();
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+    boolean enableReaderOption(boolean enable);
 }
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 1307dfc..4658630 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -17,6 +17,7 @@
 package android.nfc;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1826,6 +1827,97 @@
     }
 
     /**
+     * Sets NFC Reader option feature.
+     * <p>This API is for the Settings application.
+     * @return True if successful
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION)
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public boolean enableReaderOption(boolean enable) {
+        if (!sHasNfcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.enableReaderOption(enable);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.enableReaderOption(enable);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Checks if the device supports NFC Reader option functionality.
+     *
+     * @return True if device supports NFC Reader option, false otherwise
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION)
+    public boolean isReaderOptionSupported() {
+        if (!sHasNfcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.isReaderOptionSupported();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.isReaderOptionSupported();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Checks NFC Reader option feature is enabled.
+     *
+     * @return True if NFC Reader option  is enabled, false otherwise
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     * @throws UnsupportedOperationException if device doesn't support
+     *         NFC Reader option functionality. {@link #isReaderOptionSupported}
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION)
+    public boolean isReaderOptionEnabled() {
+        if (!sHasNfcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.isReaderOptionEnabled();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.isReaderOptionEnabled();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
      * Enable NDEF Push feature.
      * <p>This API is for the Settings application.
      * @hide
diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig
index e3faf39..55b0b42 100644
--- a/core/java/android/nfc/flags.aconfig
+++ b/core/java/android/nfc/flags.aconfig
@@ -6,3 +6,10 @@
     description: "Flag for NFC mainline changes"
     bug: "292140387"
 }
+
+flag {
+    name: "enable_nfc_reader_option"
+    namespace: "nfc"
+    description: "Flag for NFC reader option API changes"
+    bug: "291187960"
+}
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 092923e..6a4ec9b 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -16,7 +16,10 @@
 
 package android.os;
 
+import static android.os.Flags.FLAG_STATE_OF_HEALTH_PUBLIC;
+
 import android.Manifest.permission;
+import android.annotation.FlaggedApi;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -354,17 +357,11 @@
     public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9;
 
     /**
-     *
-     * Percentage representing the measured battery state of health (remaining
-     * estimated full charge capacity relative to the rated capacity in %).
-     *
-     * <p class="note">
-     * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
-     *
-     * @hide
+     * Percentage representing the measured battery state of health.
+     * This is the remaining estimated full charge capacity relative
+     * to the rated capacity in %.
      */
-    @RequiresPermission(permission.BATTERY_STATS)
-    @SystemApi
+    @FlaggedApi(FLAG_STATE_OF_HEALTH_PUBLIC)
     public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10;
 
     private final Context mContext;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 8482945..e85b7bf 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1854,7 +1854,7 @@
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
         public HistoryItem next;
 
-        // The time of this event in milliseconds, as per SystemClock.elapsedRealtime().
+        // The time of this event in milliseconds, as per MonotonicClock.monotonicTime().
         @UnsupportedAppUsage
         public long time;
 
diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java
index 955fad3..3abe9a0 100644
--- a/core/java/android/os/BatteryStatsManager.java
+++ b/core/java/android/os/BatteryStatsManager.java
@@ -520,8 +520,9 @@
      * @param uid calling package uid
      * @param reason why Bluetooth has been turned on
      * @param packageName package responsible for this change
-     * @Deprecated Bluetooth self report its state and no longer call this
+     * @deprecated Bluetooth self report its state and no longer call this
      */
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void reportBluetoothOn(int uid, int reason, @NonNull String packageName) {
     }
@@ -532,8 +533,9 @@
      * @param uid calling package uid
      * @param reason why Bluetooth has been turned on
      * @param packageName package responsible for this change
-     * @Deprecated Bluetooth self report its state and no longer call this
+     * @deprecated Bluetooth self report its state and no longer call this
      */
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void reportBluetoothOff(int uid, int reason, @NonNull String packageName) {
     }
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 49d7e8b..32840d4 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -300,6 +300,7 @@
          * @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis()
          * @param toTimestamp Inclusive ending timestamp, as per System.currentTimeMillis()
          */
+        // TODO(b/298459065): switch to monotonic clock
         public Builder aggregateSnapshots(long fromTimestamp, long toTimestamp) {
             mFromTimestamp = fromTimestamp;
             mToTimestamp = toTimestamp;
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 086b0e5..58def6e 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -143,10 +143,11 @@
          */
         public void onError(@BugreportErrorCode int errorCode) {}
 
-        /** Called when taking bugreport finishes successfully.
+        /**
+         * Called when taking bugreport finishes successfully.
          *
          * <p>This callback will be invoked if the
-         * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set.
+         * {@code BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set.
          */
         public void onFinished() {}
 
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 4174c1c..fce715a 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -2670,7 +2670,7 @@
 
         /**
          * Called when overall thermal throttling status changed.
-         * @param status defined in {@link android.os.Temperature}.
+         * @param status the status
          */
         void onThermalStatusChanged(@ThermalStatus int status);
     }
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/RemoteException.java b/core/java/android/os/RemoteException.java
index 970f419..ace5f7e 100644
--- a/core/java/android/os/RemoteException.java
+++ b/core/java/android/os/RemoteException.java
@@ -66,7 +66,7 @@
     /**
      * Rethrow this exception when we know it came from the system server. This
      * gives us an opportunity to throw a nice clean
-     * {@link DeadSystemRuntimeException} signal to avoid spamming logs with
+     * {@code DeadSystemRuntimeException} signal to avoid spamming logs with
      * misleading stack traces.
      * <p>
      * Apps making calls into the system server may end up persisting internal
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index a95e66d..37559b3 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -1,6 +1,13 @@
 package: "android.os"
 
 flag {
+    name: "state_of_health_public"
+    namespace: "system_sw_battery"
+    description: "Feature flag for making state_of_health a public api."
+    bug: "288842045"
+}
+
+flag {
     name: "disallow_cellular_null_ciphers_restriction"
     namespace: "cellular_security"
     description: "Guards a new UserManager user restriction that admins can use to require cellular encryption on their managed devices."
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 d8e60c8..66ad12c 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -19,4 +19,21 @@
     name: "haptics_customization_ringtone_v2_enabled"
     description: "Enables the usage of the new RingtoneV2 class"
     bug: "241918098"
-}
\ No newline at end of file
+}
+
+flag {
+    namespace: "haptics"
+    name: "enable_vibration_serialization_apis"
+    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/os/vibrator/persistence/ParsedVibration.java b/core/java/android/os/vibrator/persistence/ParsedVibration.java
index ded74ea..3d1deea 100644
--- a/core/java/android/os/vibrator/persistence/ParsedVibration.java
+++ b/core/java/android/os/vibrator/persistence/ParsedVibration.java
@@ -16,6 +16,7 @@
 
 package android.os.vibrator.persistence;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
@@ -34,6 +35,7 @@
  *
  * @hide
  */
+@FlaggedApi(android.os.vibrator.Flags.FLAG_ENABLE_VIBRATION_SERIALIZATION_APIS)
 @TestApi
 public class ParsedVibration {
     private final List<VibrationEffect> mEffects;
diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
index e08cc42..fed1053 100644
--- a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
+++ b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
@@ -16,6 +16,7 @@
 
 package android.os.vibrator.persistence;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -115,6 +116,7 @@
  *
  * @hide
  */
+@FlaggedApi(android.os.vibrator.Flags.FLAG_ENABLE_VIBRATION_SERIALIZATION_APIS)
 @TestApi
 public final class VibrationXmlParser {
     private static final String TAG = "VibrationXmlParser";
diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
index 1cdfa4f..2880454 100644
--- a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
+++ b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
@@ -16,6 +16,7 @@
 
 package android.os.vibrator.persistence;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.TestApi;
@@ -42,6 +43,7 @@
  *
  * @hide
  */
+@FlaggedApi(android.os.vibrator.Flags.FLAG_ENABLE_VIBRATION_SERIALIZATION_APIS)
 @TestApi
 public final class VibrationXmlSerializer {
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 36d3180..b34e09f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1800,7 +1800,6 @@
      * Input: Nothing.
      * <p>
      * Output: Nothing.
-     * @see android.service.notification.NotificationAssistantService
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_NOTIFICATION_ASSISTANT_SETTINGS =
@@ -2507,8 +2506,8 @@
      * to the caller package.
      * <p>
      * <b>NOTE: </b> Applications should call
-     * {@link android.credentials.CredentialManager#isEnabledCredentialProviderService()}
-     * and only use this action to start an activity if they return {@code false}.
+     * {@link android.credentials.CredentialManager#isEnabledCredentialProviderService(
+     * ComponentName)} and only use this action to start an activity if they return {@code false}.
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_CREDENTIAL_PROVIDER =
@@ -4866,7 +4865,8 @@
                 "display_color_mode_vendor_hint";
 
         /**
-         * The user selected min refresh rate in frames per second.
+         * The user selected min refresh rate in frames per second. If infinite, the user wants
+         * the highest possible refresh rate.
          *
          * If this isn't set, 0 will be used.
          * @hide
@@ -4875,7 +4875,8 @@
         public static final String MIN_REFRESH_RATE = "min_refresh_rate";
 
         /**
-         * The user selected peak refresh rate in frames per second.
+         * The user selected peak refresh rate in frames per second. If infinite, the user wants
+         * the highest possible refresh rate.
          *
          * If this isn't set, the system falls back to a device specific default.
          * @hide
@@ -5352,6 +5353,37 @@
         public static final Uri NOTIFICATION_SOUND_CACHE_URI = getUriFor(NOTIFICATION_SOUND_CACHE);
 
         /**
+         * When enabled, notifications attention effects: sound, vibration, flashing
+         * will have a cooldown timer.
+         *
+         * The value 1 - enable, 0 - disable
+         * @hide
+         */
+        public static final String NOTIFICATION_COOLDOWN_ENABLED =
+            "notification_cooldown_enabled";
+
+        /**
+         * When enabled, notification cooldown will apply to all notifications.
+         * Otherwise cooldown will only apply to conversations.
+         *
+         * The value 1 - enable, 0 - disable
+         * Only valid if {@code NOTIFICATION_COOLDOWN_ENABLED} is enabled.
+         * @hide
+         */
+        public static final String NOTIFICATION_COOLDOWN_ALL =
+            "notification_cooldown_all";
+
+        /**
+         * When enabled, notification attention effects will be restricted to vibration only
+         * as long as the screen is unlocked.
+         *
+         * The value 1 - enable, 0 - disable
+         * @hide
+         */
+        public static final String NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED =
+            "notification_cooldown_vibrate_unlocked";
+
+        /**
          * Persistent store for the system-wide default alarm alert.
          *
          * @see #RINGTONE
@@ -11178,6 +11210,12 @@
         public static final String BLUETOOTH_ON_WHILE_DRIVING = "bluetooth_on_while_driving";
 
         /**
+         * Volume dialog timeout in ms.
+         * @hide
+         */
+        public static final String VOLUME_DIALOG_DISMISS_TIMEOUT = "volume_dialog_dismiss_timeout";
+
+        /**
          * What behavior should be invoked when the volume hush gesture is triggered
          * One of VOLUME_HUSH_OFF, VOLUME_HUSH_VIBRATE, VOLUME_HUSH_MUTE.
          *
@@ -12336,7 +12374,7 @@
          * Value to specify if the device's UTC system clock should be set automatically, e.g. using
          * telephony signals like NITZ, or other sources like GNSS or NTP.
          *
-         * <p>Prefer {@link android.app.time.TimeManager} API calls to determine the state of
+         * <p>Prefer {@code android.app.time.TimeManager} API calls to determine the state of
          * automatic time detection instead of directly observing this setting as it may be ignored
          * by the time_detector service under various conditions.
          *
@@ -12349,7 +12387,7 @@
          * Value to specify if the device's time zone system property should be set automatically,
          * e.g. using telephony signals like MCC and NITZ, or other mechanisms like the location.
          *
-         * <p>Prefer {@link android.app.time.TimeManager} API calls to determine the state of
+         * <p>Prefer {@code android.app.time.TimeManager} API calls to determine the state of
          * automatic time zone detection instead of directly observing this setting as it may be
          * ignored by the time_zone_detector service under various conditions.
          *
@@ -12463,6 +12501,13 @@
                 "wireless_charging_started_sound";
 
         /**
+         * Whether to auto enable reverse charging once plugged-in.
+         * @hide
+         */
+        public static final String REVERSE_CHARGING_AUTO_ON =
+                "settings_key_reverse_charging_auto_turn_on";
+
+        /**
          * URI for "wired charging started" sound.
          * @hide
          */
diff --git a/core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl b/core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl
deleted file mode 100644
index dbffd5f..0000000
--- a/core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * Copyright (c) 2016, The Android Open 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.security.keymaster;
-
-import android.security.keymaster.KeyAttestationApplicationId;
-import android.security.keymaster.KeyAttestationPackageInfo;
-import android.content.pm.Signature;
-
-/**
- * This must be kept manually in sync with system/security/keystore until AIDL
- * can generate both Java and C++ bindings.
- *
- * @hide
- */
-interface IKeyAttestationApplicationIdProvider {
-    /* keep in sync with /system/security/keystore/keystore_attestation_id.cpp */
-    KeyAttestationApplicationId getKeyAttestationApplicationId(int uid);
-}
diff --git a/core/java/android/security/keymaster/KeyAttestationApplicationId.aidl b/core/java/android/security/keymaster/KeyAttestationApplicationId.aidl
deleted file mode 100644
index 9f6ff58..0000000
--- a/core/java/android/security/keymaster/KeyAttestationApplicationId.aidl
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (c) 2016, The Android Open 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.security.keymaster;
-
-/* The cpp_header is relative to system/security/keystore/include
- * Link against libkeystore_binder to make use of the native implementation of this Parcelable.
- */
-parcelable KeyAttestationApplicationId cpp_header "keystore/KeyAttestationApplicationId.h";
diff --git a/core/java/android/security/keymaster/KeyAttestationApplicationId.java b/core/java/android/security/keymaster/KeyAttestationApplicationId.java
deleted file mode 100644
index 670f30e1b..0000000
--- a/core/java/android/security/keymaster/KeyAttestationApplicationId.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open 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.security.keymaster;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * @hide
- * The information aggregated by this class is used by keystore to identify a caller of the
- * keystore API toward a remote party. It aggregates multiple PackageInfos because keystore
- * can only determine a caller by uid granularity, and a uid can be shared by multiple packages.
- * The remote party must decide if it trusts all of the packages enough to consider the
- * confidentiality of the key material in question intact.
- */
-public class KeyAttestationApplicationId implements Parcelable {
-    private final KeyAttestationPackageInfo[] mAttestationPackageInfos;
-
-    /**
-     * @param mAttestationPackageInfos
-     */
-    public KeyAttestationApplicationId(KeyAttestationPackageInfo[] mAttestationPackageInfos) {
-        super();
-        this.mAttestationPackageInfos = mAttestationPackageInfos;
-    }
-
-    /**
-     * @return the mAttestationPackageInfos
-     */
-    public KeyAttestationPackageInfo[] getAttestationPackageInfos() {
-        return mAttestationPackageInfos;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeTypedArray(mAttestationPackageInfos, flags);
-    }
-
-    public static final @android.annotation.NonNull Parcelable.Creator<KeyAttestationApplicationId> CREATOR
-            = new Parcelable.Creator<KeyAttestationApplicationId>() {
-        @Override
-        public KeyAttestationApplicationId createFromParcel(Parcel source) {
-            return new KeyAttestationApplicationId(source);
-        }
-
-        @Override
-        public KeyAttestationApplicationId[] newArray(int size) {
-            return new KeyAttestationApplicationId[size];
-        }
-    };
-
-    KeyAttestationApplicationId(Parcel source) {
-        mAttestationPackageInfos = source.createTypedArray(KeyAttestationPackageInfo.CREATOR);
-    }
-}
diff --git a/core/java/android/security/keymaster/KeyAttestationPackageInfo.aidl b/core/java/android/security/keymaster/KeyAttestationPackageInfo.aidl
deleted file mode 100644
index f8b843b..0000000
--- a/core/java/android/security/keymaster/KeyAttestationPackageInfo.aidl
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (c) 2016, The Android Open 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.security.keymaster;
-
-/* The cpp_header is relative to system/security/keystore/include
- * Link against libkeystore_binder to make use of the native implementation of this Parcelable.
- */
-parcelable KeyAttestationPackageInfo cpp_header "keystore/KeyAttestationPackageInfo.h";
diff --git a/core/java/android/security/keymaster/KeyAttestationPackageInfo.java b/core/java/android/security/keymaster/KeyAttestationPackageInfo.java
deleted file mode 100644
index c0b8d8d..0000000
--- a/core/java/android/security/keymaster/KeyAttestationPackageInfo.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open 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.security.keymaster;
-
-import android.content.pm.Signature;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * @hide
- * This class constitutes and excerpt from the PackageManager's PackageInfo for the purpose of
- * key attestation. It is part of the KeyAttestationApplicationId, which is used by
- * keystore to identify the caller of the keystore API towards a remote party.
- */
-public class KeyAttestationPackageInfo implements Parcelable {
-    private final String mPackageName;
-    private final long mPackageVersionCode;
-    private final Signature[] mPackageSignatures;
-
-    /**
-     * @param mPackageName
-     * @param mPackageVersionCode
-     * @param mPackageSignatures
-     */
-    public KeyAttestationPackageInfo(
-            String mPackageName, long mPackageVersionCode, Signature[] mPackageSignatures) {
-        super();
-        this.mPackageName = mPackageName;
-        this.mPackageVersionCode = mPackageVersionCode;
-        this.mPackageSignatures = mPackageSignatures;
-    }
-    /**
-     * @return the mPackageName
-     */
-    public String getPackageName() {
-        return mPackageName;
-    }
-    /**
-     * @return the mPackageVersionCode
-     */
-    public long getPackageVersionCode() {
-        return mPackageVersionCode;
-    }
-    /**
-     * @return the mPackageSignatures
-     */
-    public Signature[] getPackageSignatures() {
-        return mPackageSignatures;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(mPackageName);
-        dest.writeLong(mPackageVersionCode);
-        dest.writeTypedArray(mPackageSignatures, flags);
-    }
-
-    public static final @android.annotation.NonNull Parcelable.Creator<KeyAttestationPackageInfo> CREATOR
-            = new Parcelable.Creator<KeyAttestationPackageInfo>() {
-        @Override
-        public KeyAttestationPackageInfo createFromParcel(Parcel source) {
-            return new KeyAttestationPackageInfo(source);
-        }
-
-        @Override
-        public KeyAttestationPackageInfo[] newArray(int size) {
-            return new KeyAttestationPackageInfo[size];
-        }
-    };
-
-    private KeyAttestationPackageInfo(Parcel source) {
-        mPackageName = source.readString();
-        mPackageVersionCode = source.readLong();
-        mPackageSignatures = source.createTypedArray(Signature.CREATOR);
-    }
-}
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 115894f..a29bf7a 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -18,6 +18,7 @@
 
 import static android.view.autofill.Helper.sDebug;
 
+import android.annotation.Hide;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -26,6 +27,7 @@
 import android.annotation.TestApi;
 import android.content.ClipData;
 import android.content.IntentSender;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArrayMap;
@@ -187,6 +189,9 @@
     @Nullable private final InlinePresentation mInlinePresentation;
     @Nullable private final InlinePresentation mInlineTooltipPresentation;
     private final IntentSender mAuthentication;
+
+    @Nullable private final Bundle mAuthenticationExtras;
+
     @Nullable String mId;
 
     /**
@@ -224,6 +229,7 @@
         mInlinePresentation = inlinePresentation;
         mInlineTooltipPresentation = inlineTooltipPresentation;
         mAuthentication = authentication;
+        mAuthenticationExtras = null;
         mId = id;
     }
 
@@ -246,6 +252,7 @@
         mInlinePresentation = dataset.mInlinePresentation;
         mInlineTooltipPresentation = dataset.mInlineTooltipPresentation;
         mAuthentication = dataset.mAuthentication;
+        mAuthenticationExtras = dataset.mAuthenticationExtras;
         mId = dataset.mId;
         mAutofillDatatypes = dataset.mAutofillDatatypes;
     }
@@ -264,6 +271,7 @@
         mInlinePresentation = builder.mInlinePresentation;
         mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
         mAuthentication = builder.mAuthentication;
+        mAuthenticationExtras = builder.mAuthenticationExtras;
         mId = builder.mId;
         mAutofillDatatypes = builder.mAutofillDatatypes;
     }
@@ -345,6 +353,12 @@
     }
 
     /** @hide */
+    @Hide
+    public @Nullable Bundle getAuthenticationExtras() {
+        return mAuthenticationExtras;
+    }
+
+    /** @hide */
     @TestApi
     public boolean isEmpty() {
         return mFieldIds == null || mFieldIds.isEmpty();
@@ -401,6 +415,9 @@
         if (mAuthentication != null) {
             builder.append(", hasAuthentication");
         }
+        if (mAuthenticationExtras != null) {
+            builder.append(", hasAuthenticationExtras");
+        }
         if (mAutofillDatatypes != null) {
             builder.append(", autofillDatatypes=").append(mAutofillDatatypes);
         }
@@ -454,6 +471,8 @@
         @Nullable private InlinePresentation mInlinePresentation;
         @Nullable private InlinePresentation mInlineTooltipPresentation;
         private IntentSender mAuthentication;
+
+        private Bundle mAuthenticationExtras;
         private boolean mDestroyed;
         @Nullable private String mId;
 
@@ -624,6 +643,25 @@
         }
 
         /**
+         * Sets extras to be associated with the {@code authentication} intent sender, to be
+         * set on the intent that is fired through the intent sender.
+         *
+         * Autofill providers can set any extras they wish to receive directly on the intent
+         * that is used to create the {@code authentication}. This is an internal API, to be
+         * used by the platform to associate data with a given dataset. These extras will be
+         * merged with the {@code clientState} and sent as part of the fill in intent when
+         * the {@code authentication} intentSender is invoked.
+         *
+         * @hide
+         */
+        @Hide
+        public @NonNull Builder setAuthenticationExtras(@Nullable Bundle authenticationExtra) {
+            throwIfDestroyed();
+            mAuthenticationExtras = authenticationExtra;
+            return this;
+        }
+
+        /**
          * Sets the id for the dataset so its usage can be tracked.
          *
          * <p>Dataset usage can be tracked for 2 purposes:
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index 8cf2ce4..7ec1483 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -97,8 +97,6 @@
      */
     public static final @RequestFlags int FLAG_VIEW_NOT_FOCUSED = 0x10;
 
-    // The flag value 0x20 has been defined in AutofillManager.
-
     /**
      * Indicates the request supports fill dialog presentation for the fields, the
      * system will send the request when the activity just started.
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 3fb94ee..7ea74d3 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -406,7 +406,7 @@
          *
          * Call this when a field has been detected with a type.
          *
-         * Altough similiarly named with {@link setFieldClassificationIds},
+         * Altough similiarly named with {@link #setFieldClassificationIds},
          * it provides a different functionality - setFieldClassificationIds should
          * be used when a field is only suspected to be Autofillable.
          * This method should be used when a field is certainly Autofillable
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 954cc96..53706cd3 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -813,7 +813,7 @@
          * If no {@link #Builder(int, AutofillId[]) required ids},
          * or {@link #setOptionalIds(AutofillId[]) optional ids}, or {@link #FLAG_DELAY_SAVE}
          * were set, Save Dialog will only be triggered if platform detection is enabled, which
-         * is indicated when {@link FillRequest.getHints()} is not empty.
+         * is indicated when {@link FillRequest#getHints()} is not empty.
          */
         public SaveInfo build() {
             throwIfDestroyed();
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/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 07f8aac..759953e 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -896,8 +896,7 @@
      * <p>This method will throw a security exception if you don't have access to notifications
      * for the given user.</p>
      * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
-     * device} or be the {@link NotificationAssistantService notification assistant} in order to
-     * use this method.
+     * device} or be the notification assistant in order to use this method.
      *
      * @param pkg The package to retrieve channels for.
      */
@@ -920,8 +919,7 @@
      * <p>This method will throw a security exception if you don't have access to notifications
      * for the given user.</p>
      * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
-     * device} or be the {@link NotificationAssistantService notification assistant} in order to
-     * use this method.
+     * device} or be the notification assistant in order to use this method.
      *
      * @param pkg The package to retrieve channel groups for.
      */
@@ -2001,15 +1999,28 @@
 
         /**
          * Returns a list of smart {@link Notification.Action} that can be added by the
-         * {@link NotificationAssistantService}
+         * notification assistant.
          */
         public @NonNull List<Notification.Action> getSmartActions() {
             return mSmartActions == null ? Collections.emptyList() : mSmartActions;
         }
 
+
         /**
-         * Returns a list of smart replies that can be added by the
-         * {@link NotificationAssistantService}
+         * Sets the smart {@link Notification.Action} objects.
+         *
+         * Should ONLY be used in cases where smartActions need to be removed from, then restored
+         * on, Ranking objects during Parceling, when they are transmitted between processes via
+         * Shared Memory.
+         *
+         * @hide
+         */
+        public void setSmartActions(@Nullable ArrayList<Notification.Action> smartActions) {
+            mSmartActions = smartActions;
+        }
+
+        /**
+         * Returns a list of smart replies that can be added by the notification assistant.
          */
         public @NonNull List<CharSequence> getSmartReplies() {
             return mSmartReplies == null ? Collections.emptyList() : mSmartReplies;
@@ -2353,11 +2364,9 @@
 
         /**
          * Get a reference to the actual Ranking object corresponding to the key.
-         * Used only by unit tests.
          *
          * @hide
          */
-        @VisibleForTesting
         public Ranking getRawRankingObject(String key) {
             return mRankings.get(key);
         }
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index f3b4c6d..c82a4ca 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -17,7 +17,8 @@
 
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
-import android.annotation.TestApi;
+import android.app.Notification;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SharedMemory;
@@ -25,17 +26,20 @@
 import android.system.OsConstants;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
 
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Represents an update to notification rankings.
+ *
  * @hide
  */
 @SuppressLint({"ParcelNotFinal", "ParcelCreator"})
-@TestApi
 public class NotificationRankingUpdate implements Parcelable {
     private final NotificationListenerService.RankingMap mRankingMap;
 
@@ -64,6 +68,7 @@
                 // The ranking map should be stored in shared memory when it is parceled, so we
                 // unwrap the SharedMemory object.
                 mRankingMapFd = in.readParcelable(getClass().getClassLoader(), SharedMemory.class);
+                Bundle smartActionsBundle = in.readBundle(getClass().getClassLoader());
 
                 // In the case that the ranking map can't be read, readParcelable may return null.
                 // In this case, we set mRankingMap to null;
@@ -82,15 +87,20 @@
                 mapParcel.unmarshall(payload, 0, payload.length);
                 mapParcel.setDataPosition(0);
 
-                mRankingMap = mapParcel.readParcelable(getClass().getClassLoader(),
-                        android.service.notification.NotificationListenerService.RankingMap.class);
+                mRankingMap =
+                        mapParcel.readParcelable(
+                                getClass().getClassLoader(),
+                                NotificationListenerService.RankingMap.class);
+
+                addSmartActionsFromBundleToRankingMap(smartActionsBundle);
+
             } catch (ErrnoException e) {
                 // TODO(b/284297289): remove throw when associated flag is moved to droidfood, to
                 // avoid crashes; change to Log.wtf.
                 throw new RuntimeException(e);
             } finally {
                 mapParcel.recycle();
-                if (buffer != null) {
+                if (buffer != null && mRankingMapFd != null) {
                     mRankingMapFd.unmap(buffer);
                     mRankingMapFd.close();
                 }
@@ -102,10 +112,34 @@
     }
 
     /**
-     * Confirms that the SharedMemory file descriptor is closed. Should only be used for testing.
+     * For each key in the rankingMap, extracts lists of smart actions stored in the provided
+     * bundle and adds them to the corresponding Ranking object in the provided ranking
+     * map, then returns the rankingMap.
+     *
      * @hide
      */
-    @TestApi
+    private void addSmartActionsFromBundleToRankingMap(Bundle smartActionsBundle) {
+        if (smartActionsBundle == null) {
+            return;
+        }
+
+        String[] rankingMapKeys = mRankingMap.getOrderedKeys();
+        for (int i = 0; i < rankingMapKeys.length; i++) {
+            String key = rankingMapKeys[i];
+            ArrayList<Notification.Action> smartActions =
+                    smartActionsBundle.getParcelableArrayList(key, Notification.Action.class);
+            // Get the ranking object from the ranking map.
+            NotificationListenerService.Ranking ranking = mRankingMap.getRawRankingObject(key);
+            ranking.setSmartActions(smartActions);
+        }
+    }
+
+    /**
+     * Confirms that the SharedMemory file descriptor is closed. Should only be used for testing.
+     *
+     * @hide
+     */
+    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
     public final boolean isFdNotNullAndClosed() {
         return mRankingMapFd != null && mRankingMapFd.getFd() == -1;
     }
@@ -145,9 +179,45 @@
         if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
                 SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
             final Parcel mapParcel = Parcel.obtain();
+            ArrayList<NotificationListenerService.Ranking> marshalableRankings = new ArrayList<>();
+            Bundle smartActionsBundle = new Bundle();
+
+            // We need to separate the SmartActions from the RankingUpdate objects.
+            // SmartActions can contain PendingIntents, which cannot be marshalled,
+            // so we extract them to send separately.
+            String[] rankingMapKeys = mRankingMap.getOrderedKeys();
+            for (int i = 0; i < rankingMapKeys.length; i++) {
+                String key = rankingMapKeys[i];
+                NotificationListenerService.Ranking ranking = mRankingMap.getRawRankingObject(key);
+
+                // Removes the SmartActions and stores them in a separate map.
+                // Note that getSmartActions returns a Collections.emptyList() if there are no
+                // smart actions, and we don't want to needlessly store an empty list object, so we
+                // check for null before storing.
+                List<Notification.Action> smartActions = ranking.getSmartActions();
+                if (!smartActions.isEmpty()) {
+                    smartActionsBundle.putParcelableList(key, smartActions);
+                }
+
+                // Create a copy of the ranking object that doesn't have the smart actions.
+                NotificationListenerService.Ranking rankingCopy =
+                        new NotificationListenerService.Ranking();
+                rankingCopy.populate(ranking);
+                rankingCopy.setSmartActions(null);
+                marshalableRankings.add(rankingCopy);
+            }
+
+            // Create a new marshalable RankingMap.
+            NotificationListenerService.RankingMap marshalableRankingMap =
+                    new NotificationListenerService.RankingMap(
+                            marshalableRankings.toArray(
+                                    new NotificationListenerService.Ranking[0]
+                            )
+                    );
+
             try {
                 // Parcels the ranking map and measures its size.
-                mapParcel.writeParcelable(mRankingMap, flags);
+                mapParcel.writeParcelable(marshalableRankingMap, flags);
                 int mapSize = mapParcel.dataSize();
 
                 // Creates a new SharedMemory object with enough space to hold the ranking map.
@@ -158,15 +228,14 @@
 
                 // Gets a read/write buffer mapping the entire shared memory region.
                 final ByteBuffer buffer = mRankingMapFd.mapReadWrite();
-
                 // Puts the ranking map into the shared memory region buffer.
                 buffer.put(mapParcel.marshall(), 0, mapSize);
-
                 // Protects the region from being written to, by setting it to be read-only.
                 mRankingMapFd.setProtect(OsConstants.PROT_READ);
-
                 // Puts the SharedMemory object in the parcel.
                 out.writeParcelable(mRankingMapFd, flags);
+                // Writes the Parceled smartActions separately.
+                out.writeBundle(smartActionsBundle);
             } catch (ErrnoException e) {
                 // TODO(b/284297289): remove throw when associated flag is moved to droidfood, to
                 // avoid crashes; change to Log.wtf.
@@ -180,8 +249,8 @@
     }
 
     /**
-    * @hide
-    */
+     * @hide
+     */
     public static final @android.annotation.NonNull Parcelable.Creator<NotificationRankingUpdate> CREATOR
             = new Parcelable.Creator<NotificationRankingUpdate>() {
         public NotificationRankingUpdate createFromParcel(Parcel parcel) {
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
index 4a4fd04..bf129c5 100644
--- a/core/java/android/service/quickaccesswallet/WalletCard.java
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -372,9 +372,9 @@
         }
 
         /**
-         * Set of locations this card might be useful at. If {@link
-         * PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is enabled, the card might be
-         * shown to the user when a user is near one of these locations.
+         * Set of locations this card might be useful at. If
+         * {@link android.content.pm.PackageManager#FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is
+         * enabled, the card might be shown to the user when a user is near one of these locations.
          */
         @NonNull
         public Builder setCardLocations(@NonNull List<Location> cardLocations) {
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index cabcae3..d40b39e 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -1809,7 +1809,7 @@
      * in milliseconds of the KeyEvent that triggered Assistant and
      * Intent.EXTRA_ASSIST_INPUT_DEVICE_ID (android.intent.extra.ASSIST_INPUT_DEVICE_ID)
      *  referring to the device that sent the request. Starting from Android 14, the system will
-     * add {@link VoiceInteractionService#KEY_SHOW_SESSION_ID}, the Bundle is not null. But the
+     * add {@link #KEY_SHOW_SESSION_ID}, the Bundle is not null. But the
      * application should handle null case before Android 14.
      * @param showFlags The show flags originally provided to
      * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index bb5dd7f..3ed13bb 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -536,7 +536,16 @@
     @MainThread
     public void setRecognitionListener(RecognitionListener listener) {
         checkIsCalledFromMainThread();
-        putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener));
+        if (mListener.mInternalListener == null) {
+            // This shortcut is needed because otherwise, if there's an error connecting, it never
+            // gets delivered. I.e., the onSuccess callback set up in connectToSystemService does
+            // not get called, MSG_CHANGE_LISTENER does not get executed, so the onError in the same
+            // place does not get forwarded anywhere.
+            // Thread-wise, this is safe as both this method and the handler are on the UI thread.
+            handleChangeListener(listener);
+        } else {
+            putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener));
+        }
     }
 
     /**
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index f6309f2..cf1156d 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -401,7 +401,7 @@
 
     /**
      * Listen for call disconnect causes which contains {@link DisconnectCause} and
-     * {@link PreciseDisconnectCause}.
+     * the precise disconnect cause.
      *
      * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
      * or the calling app has carrier privileges
@@ -851,8 +851,8 @@
      * subId. Otherwise, this callback applies to
      * {@link SubscriptionManager#getDefaultSubscriptionId()}.
      *
-     * @param disconnectCause {@link DisconnectCause}.
-     * @param preciseDisconnectCause {@link PreciseDisconnectCause}.
+     * @param disconnectCause the disconnect cause
+     * @param preciseDisconnectCause the precise disconnect cause
      * @deprecated Use {@link TelephonyCallback.CallDisconnectCauseListener} instead.
      */
     @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
diff --git a/core/java/android/telephony/SubscriptionPlan.java b/core/java/android/telephony/SubscriptionPlan.java
index fb2d771..7b48a16 100644
--- a/core/java/android/telephony/SubscriptionPlan.java
+++ b/core/java/android/telephony/SubscriptionPlan.java
@@ -221,7 +221,7 @@
     }
 
     /**
-     * Return an array containing all {@link NetworkType}s this SubscriptionPlan applies to.
+     * Return an array containing all network types this SubscriptionPlan applies to.
      * @see TelephonyManager for network types values
      */
     public @NonNull @NetworkType int[] getNetworkTypes() {
@@ -365,7 +365,7 @@
          * Set the network types this SubscriptionPlan applies to. By default the plan will apply
          * to all network types. An empty array means this plan applies to no network types.
          *
-         * @param networkTypes an array of all {@link NetworkType}s that apply to this plan.
+         * @param networkTypes an array of all network types that apply to this plan.
          * @see TelephonyManager for network type values
          */
         public @NonNull Builder setNetworkTypes(@NonNull @NetworkType int[] networkTypes) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 7ada058..19bcf28 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -948,8 +948,8 @@
          * subscription ID. Otherwise, this callback applies to
          * {@link SubscriptionManager#getDefaultSubscriptionId()}.
          *
-         * @param disconnectCause        {@link DisconnectCause}.
-         * @param preciseDisconnectCause {@link PreciseDisconnectCause}.
+         * @param disconnectCause        the disconnect cause
+         * @param preciseDisconnectCause the precise disconnect cause
          */
         @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
         void onCallDisconnectCauseChanged(@Annotation.DisconnectCauses int disconnectCause,
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index e2c5539..0063d13 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -238,7 +238,7 @@
     }
 
     /**
-     * To check the SDK version for {@link #listenFromListener}.
+     * To check the SDK version for {@code #listenFromListener}.
      */
     @ChangeId
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P)
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 2b3a081..a0cd074 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -16,6 +16,10 @@
 
 package android.text;
 
+import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
+import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
+
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -280,6 +284,7 @@
          * @see android.widget.TextView#setLineBreakWordStyle
          */
         @NonNull
+        @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
         public Builder setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) {
             mLineBreakConfig = lineBreakConfig;
             return this;
@@ -303,6 +308,7 @@
          * @see Layout.Builder#setUseBoundsForWidth(boolean)
          */
         @NonNull
+        @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
         public Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
             mUseBoundsForWidth = useBoundsForWidth;
             return this;
@@ -1268,7 +1274,7 @@
     }
 
     /**
-     * Gets the {@link LineBreakconfig} used in this DynamicLayout.
+     * Gets the {@link LineBreakConfig} used in this DynamicLayout.
      * Use this only to consult the LineBreakConfig's properties and not
      * to change them.
      *
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index c585734..94c8eaf 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -29,6 +29,7 @@
 import android.graphics.fonts.FontFamily.Builder.VariableFontFamilyType;
 import android.graphics.fonts.FontStyle;
 import android.graphics.fonts.FontVariationAxis;
+import android.icu.util.ULocale;
 import android.os.Build;
 import android.os.LocaleList;
 import android.os.Parcel;
@@ -39,6 +40,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
 
 
@@ -58,6 +60,7 @@
     private final @NonNull List<FontFamily> mFamilies;
     private final @NonNull List<Alias> mAliases;
     private final @NonNull List<NamedFamilyList> mNamedFamilyLists;
+    private final @NonNull List<Customization.LocaleFallback> mLocaleFallbackCustomizations;
     private final long mLastModifiedTimeMillis;
     private final int mConfigVersion;
 
@@ -71,10 +74,12 @@
      */
     public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases,
             @NonNull List<NamedFamilyList> namedFamilyLists,
+            @NonNull List<Customization.LocaleFallback> localeFallbackCustomizations,
             long lastModifiedTimeMillis, @IntRange(from = 0) int configVersion) {
         mFamilies = families;
         mAliases = aliases;
         mNamedFamilyLists = namedFamilyLists;
+        mLocaleFallbackCustomizations = localeFallbackCustomizations;
         mLastModifiedTimeMillis = lastModifiedTimeMillis;
         mConfigVersion = configVersion;
     }
@@ -84,7 +89,8 @@
      */
     public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases,
             long lastModifiedTimeMillis, @IntRange(from = 0) int configVersion) {
-        this(families, aliases, Collections.emptyList(), lastModifiedTimeMillis, configVersion);
+        this(families, aliases, Collections.emptyList(), Collections.emptyList(),
+                lastModifiedTimeMillis, configVersion);
     }
 
 
@@ -113,6 +119,18 @@
     }
 
     /**
+     * Returns a locale fallback customizations.
+     *
+     * This field is used for creating the system fallback in the system server. This field is
+     * always empty in the application process.
+     *
+     * @hide
+     */
+    public @NonNull List<Customization.LocaleFallback> getLocaleFallbackCustomizations() {
+        return mLocaleFallbackCustomizations;
+    }
+
+    /**
      * Returns the last modified time in milliseconds.
      *
      * This is a value of {@link System#currentTimeMillis()} when the system font configuration was
@@ -169,7 +187,9 @@
             source.readTypedList(familyLists, NamedFamilyList.CREATOR);
             long lastModifiedDate = source.readLong();
             int configVersion = source.readInt();
-            return new FontConfig(families, aliases, familyLists, lastModifiedDate, configVersion);
+            return new FontConfig(families, aliases, familyLists,
+                    Collections.emptyList(),  // Don't need to pass customization to API caller.
+                    lastModifiedDate, configVersion);
         }
 
         @Override
@@ -813,4 +833,129 @@
                     + '}';
         }
     }
+
+    /** @hide */
+    public static class Customization {
+        private Customization() {}  // Singleton
+
+        /**
+         * A class that represents customization of locale fallback
+         *
+         * This class represents a vendor customization of new-locale-family.
+         *
+         * <pre>
+         * <family customizationType="new-locale-family" operation="prepend" lang="ja-JP">
+         *     <font weight="400" style="normal">MyAlternativeFont.ttf
+         *         <axis tag="wght" stylevalue="400"/>
+         *     </font>
+         * </family>
+         * </pre>
+         *
+         * The operation can be one of prepend, replace or append. The operation prepend means that
+         * the new font family is inserted just before the original font family. The original font
+         * family is still in the fallback. The operation replace means that the original font
+         * family is replaced with new font family. The original font family is removed from the
+         * fallback. The operation append means that the new font family is inserted just after the
+         * original font family. The original font family is still in the fallback.
+         *
+         * The lang attribute is a BCP47 compliant language tag. The font fallback mainly uses ISO
+         * 15924 script code for matching. If the script code is missing, most likely script code
+         * will be used.
+         */
+        public static class LocaleFallback {
+            private final Locale mLocale;
+            private final int mOperation;
+            private final FontFamily mFamily;
+            private final String mScript;
+
+            public static final int OPERATION_PREPEND = 0;
+            public static final int OPERATION_APPEND = 1;
+            public static final int OPERATION_REPLACE = 2;
+
+            /** @hide */
+            @Retention(SOURCE)
+            @IntDef(prefix = { "OPERATION_" }, value = {
+                    OPERATION_PREPEND,
+                    OPERATION_APPEND,
+                    OPERATION_REPLACE
+            })
+            public @interface Operation {}
+
+
+            public LocaleFallback(@NonNull Locale locale, @Operation int operation,
+                    @NonNull FontFamily family) {
+                mLocale = locale;
+                mOperation = operation;
+                mFamily = family;
+                mScript = resolveScript(locale);
+            }
+
+            /**
+             * A customization target locale.
+             * @return a locale
+             */
+            public @NonNull Locale getLocale() {
+                return mLocale;
+            }
+
+            /**
+             * An operation to be applied to the original font family.
+             *
+             * The operation can be one of {@link #OPERATION_PREPEND}, {@link #OPERATION_REPLACE} or
+             * {@link #OPERATION_APPEND}.
+             *
+             * The operation prepend ({@link #OPERATION_PREPEND}) means that the new font family is
+             * inserted just before the original font family. The original font family is still in
+             * the fallback.
+             *
+             * The operation replace ({@link #OPERATION_REPLACE}) means that the original font
+             * family is replaced with new font family. The original font family is removed from the
+             * fallback.
+             *
+             * The operation append ({@link #OPERATION_APPEND}) means that the new font family is
+             * inserted just after the original font family. The original font family is still in
+             * the fallback.
+             *
+             * @return an operation.
+             */
+            public @Operation int getOperation() {
+                return mOperation;
+            }
+
+            /**
+             * Returns a family to be inserted or replaced to the fallback.
+             *
+             * @return a family
+             */
+            public @NonNull FontFamily getFamily() {
+                return mFamily;
+            }
+
+            /**
+             * Returns a script of the locale. If the script is missing in the given locale, the
+             * most likely locale is returned.
+             */
+            public @NonNull String getScript() {
+                return mScript;
+            }
+
+            @Override
+            public String toString() {
+                return "LocaleFallback{"
+                        + "mLocale=" + mLocale
+                        + ", mOperation=" + mOperation
+                        + ", mFamily=" + mFamily
+                        + '}';
+            }
+        }
+    }
+
+    /** @hide */
+    public static String resolveScript(Locale locale) {
+        String script = locale.getScript();
+        if (script != null && !script.isEmpty()) {
+            return script;
+        }
+        return ULocale.addLikelySubtags(ULocale.forLocale(locale)).getScript();
+    }
 }
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index c1d0e9b9..ac5eb3c 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -16,6 +16,9 @@
 
 package android.text;
 
+import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
+
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -416,10 +419,12 @@
      * @hide
      */
     @TestApi
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
     public interface StyleRunCallback {
         /**
          * Called when a single style run is identified.
          */
+        @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
         void onAppendStyleRun(@NonNull Paint paint,
                 @Nullable LineBreakConfig lineBreakConfig, @IntRange(from = 0) int length,
                 boolean isRtl);
@@ -427,6 +432,7 @@
         /**
          * Called when a single replacement run is identified.
          */
+        @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
         void onAppendReplacementRun(@NonNull Paint paint,
                 @IntRange(from = 0) int length, @Px @FloatRange(from = 0) float width);
     }
@@ -488,6 +494,7 @@
     @SuppressLint("ExecutorRegistration")
     @TestApi
     @NonNull
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
     public static MeasuredParagraph buildForStaticLayoutTest(
             @NonNull TextPaint paint,
             @Nullable LineBreakConfig lineBreakConfig,
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index e3c72c9..01279ce 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -16,6 +16,9 @@
 
 package android.text;
 
+import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
+
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -439,6 +442,7 @@
          * @see Layout.Builder#setUseBoundsForWidth(boolean)
          */
         @NonNull
+        @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
         public Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
             mUseBoundsForWidth = useBoundsForWidth;
             return this;
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 536e3cc..9edf298 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -62,6 +62,18 @@
     };
 
     /**
+     * List of the default values of the text flags.
+     *
+     * The order must be the same to the TEXT_ACONFIG_FLAGS.
+     */
+    public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = {
+            Flags.deprecateFontsXml(),
+            Flags.noBreakNoHyphenationSpan(),
+            Flags.phraseStrictFallback(),
+            Flags.useBoundsForWidth(),
+    };
+
+    /**
      * Get a key for the feature flag.
      */
     public static String getKeyForFlag(@NonNull String flag) {
diff --git a/core/java/android/text/WordSegmentFinder.java b/core/java/android/text/WordSegmentFinder.java
index be002f3..b0a70ea 100644
--- a/core/java/android/text/WordSegmentFinder.java
+++ b/core/java/android/text/WordSegmentFinder.java
@@ -24,7 +24,7 @@
 
 /**
  * Implementation of {@link SegmentFinder} using words as the text segment. Word boundaries are
- * found using {@link WordIterator}. Whitespace characters are excluded, so they are not included in
+ * found using {@code WordIterator}. Whitespace characters are excluded, so they are not included in
  * any text segments.
  *
  * <p>For example, the text "Hello, World!" would be subdivided into four text segments: "Hello",
diff --git a/core/java/android/text/flags/custom_locale_fallback.aconfig b/core/java/android/text/flags/custom_locale_fallback.aconfig
new file mode 100644
index 0000000..52fe883
--- /dev/null
+++ b/core/java/android/text/flags/custom_locale_fallback.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.text.flags"
+
+flag {
+  name: "custom_locale_fallback"
+  namespace: "text"
+  description: "A feature flag that adds custom locale fallback to the vendor customization XML. This enables vendors to add their locale specific fonts, e.g. Japanese font."
+  is_fixed_read_only: true
+  bug: "278768958"
+}
diff --git a/core/java/android/text/flags/deprecate_fonts_xml.aconfig b/core/java/android/text/flags/deprecate_fonts_xml.aconfig
index 58dc210..5362138 100644
--- a/core/java/android/text/flags/deprecate_fonts_xml.aconfig
+++ b/core/java/android/text/flags/deprecate_fonts_xml.aconfig
@@ -4,5 +4,7 @@
   name: "deprecate_fonts_xml"
   namespace: "text"
   description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml"
+  # Make read only, as it could be used before the Settings provider is initialized.
+  is_fixed_read_only: true
   bug: "281769620"
 }
diff --git a/core/java/android/text/flags/fix_line_height_for_locale.aconfig b/core/java/android/text/flags/fix_line_height_for_locale.aconfig
new file mode 100644
index 0000000..8696bfa
--- /dev/null
+++ b/core/java/android/text/flags/fix_line_height_for_locale.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.text.flags"
+
+flag {
+  name: "fix_line_height_for_locale"
+  namespace: "text"
+  description: "Feature flag that preserve the line height of the TextView and EditText even if the the locale is different from Latin"
+  bug: "303326708"
+}
diff --git a/core/java/android/text/flags/optimized_font_loading.aconfig b/core/java/android/text/flags/optimized_font_loading.aconfig
new file mode 100644
index 0000000..0e4a69f
--- /dev/null
+++ b/core/java/android/text/flags/optimized_font_loading.aconfig
@@ -0,0 +1,11 @@
+package: "com.android.text.flags"
+
+flag {
+  name: "use_optimized_boottime_font_loading"
+  namespace: "text"
+  description: "Feature flag ensuring that font is loaded once and asynchronously."
+  # Make read only, as font loading is in the critical boot path which happens before the read-write
+  # flags propagate to the device.
+  is_fixed_read_only: true
+  bug: "304406888"
+}
diff --git a/core/java/android/text/style/LineBreakConfigSpan.java b/core/java/android/text/style/LineBreakConfigSpan.java
index 25c1db4d..682ffa1 100644
--- a/core/java/android/text/style/LineBreakConfigSpan.java
+++ b/core/java/android/text/style/LineBreakConfigSpan.java
@@ -71,6 +71,10 @@
             .setHyphenation(LineBreakConfig.HYPHENATION_DISABLED)
             .build();
 
+    private static final LineBreakConfig sNoBreakConfig = new LineBreakConfig.Builder()
+            .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_NO_BREAK)
+            .build();
+
     /**
      * A specialized {@link LineBreakConfigSpan} that used for preventing hyphenation.
      */
@@ -84,4 +88,24 @@
             super(sNoHyphenationConfig);
         }
     }
+
+    /**
+     * A specialized {@link LineBreakConfigSpan} that used for preventing line break.
+     *
+     * This is useful when you want to preserve some words in the same line.
+     * Note that even if this style is specified, the grapheme based line break is still performed
+     * for preventing clipping text.
+     *
+     * @see LineBreakConfigSpan
+     */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public static final class NoBreakSpan extends LineBreakConfigSpan {
+        /**
+         * Construct a new {@link NoBreakSpan}.
+         */
+        @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+        public NoBreakSpan() {
+            super(sNoBreakConfig);
+        }
+    }
 }
diff --git a/core/java/android/util/Base64.java b/core/java/android/util/Base64.java
index 92abd7c..33cc5e3 100644
--- a/core/java/android/util/Base64.java
+++ b/core/java/android/util/Base64.java
@@ -26,6 +26,7 @@
  * href="http://www.ietf.org/rfc/rfc2045.txt">2045</a> and <a
  * href="http://www.ietf.org/rfc/rfc3548.txt">3548</a>.
  */
+@android.ravenwood.annotations.RavenwoodWholeClassKeep
 public class Base64 {
     /**
      * Default values for encoder/decoder flags.
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 1ab055a..a74cbe4 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -16,9 +16,11 @@
 
 package android.view;
 
+import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_API;
 import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP;
 import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -695,6 +697,7 @@
      */
     @TestApi
     @UnsupportedAppUsage
+    @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
     public long getFrameTimeNanos() {
         synchronized (mLock) {
             if (!mCallbacksRunning) {
diff --git a/core/java/android/view/IDecorViewGestureListener.aidl b/core/java/android/view/IDecorViewGestureListener.aidl
new file mode 100644
index 0000000..1022dbf
--- /dev/null
+++ b/core/java/android/view/IDecorViewGestureListener.aidl
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+/**
+ * Listener for changes to gesture interception detector running at DecorView.
+ *
+ * {@hide}
+ */
+oneway interface IDecorViewGestureListener {
+    /**
+     * Called when a DecorView has started intercepting gesture.
+     *
+     * @param windowToken Where did this gesture interception result comes from.
+     * @param intercepted Whether the gesture interception detector has started interception.
+     */
+    void onInterceptionChanged(in IBinder windowToken, in boolean intercepted);
+}
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/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index cccac95..c10fc9f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -48,6 +48,7 @@
 import android.view.RemoteAnimationAdapter;
 import android.view.IRotationWatcher;
 import android.view.ISystemGestureExclusionListener;
+import android.view.IDecorViewGestureListener;
 import android.view.IWallpaperVisibilityListener;
 import android.view.IWindow;
 import android.view.IWindowSession;
@@ -1062,4 +1063,18 @@
      @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
              + ".permission.ACCESS_SURFACE_FLINGER)")
     boolean replaceContentOnDisplay(int displayId, in SurfaceControl sc);
+
+    /**
+     * Registers a DecorView gesture listener for a given display.
+     */
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+            + ".permission.MONITOR_INPUT)")
+    void registerDecorViewGestureListener(IDecorViewGestureListener listener, int displayId);
+
+    /**
+     * Unregisters a DecorView gesture listener for a given display.
+     */
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+            + ".permission.MONITOR_INPUT)")
+    void unregisterDecorViewGestureListener(IDecorViewGestureListener listener, int displayId);
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 83de2a0..7acf2f8 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -284,6 +284,11 @@
     oneway void reportSystemGestureExclusionChanged(IWindow window, in List<Rect> exclusionRects);
 
     /**
+     * Called when the DecorView gesture interception state has changed.
+     */
+    oneway void reportDecorViewGestureInterceptionChanged(IWindow window, in boolean intercepted);
+
+    /**
      * Called when the keep-clear areas for this window have changed.
      */
     oneway void reportKeepClearAreasChanged(IWindow window, in List<Rect> restricted,
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 be6fb31..2f3d73a 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -123,7 +123,8 @@
     private static native long nativeMirrorSurface(long mirrorOfObject);
     private static native long nativeCreateTransaction();
     private static native long nativeGetNativeTransactionFinalizer();
-    private static native void nativeApplyTransaction(long transactionObj, boolean sync);
+    private static native void nativeApplyTransaction(long transactionObj, boolean sync,
+            boolean oneWay);
     private static native void nativeMergeTransaction(long transactionObj,
             long otherTransactionObj);
     private static native void nativeClearTransaction(long transactionObj);
@@ -262,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);
@@ -1789,7 +1790,12 @@
         public float xDpi;
         public float yDpi;
 
+        // Some modes have peak refresh rate lower than the panel vsync rate.
         public float refreshRate;
+        // Fixed rate of vsync deadlines for the panel.
+        // This can be higher then the peak refresh rate for some panel technologies
+        // See: VrrConfig.aidl
+        public float vsyncRate;
         public long appVsyncOffsetNanos;
         public long presentationDeadlineNanos;
         public int[] supportedHdrTypes;
@@ -1810,6 +1816,7 @@
                     + ", xDpi=" + xDpi
                     + ", yDpi=" + yDpi
                     + ", refreshRate=" + refreshRate
+                    + ", vsyncRate=" + vsyncRate
                     + ", appVsyncOffsetNanos=" + appVsyncOffsetNanos
                     + ", presentationDeadlineNanos=" + presentationDeadlineNanos
                     + ", supportedHdrTypes=" + Arrays.toString(supportedHdrTypes)
@@ -1827,6 +1834,7 @@
                     && Float.compare(that.xDpi, xDpi) == 0
                     && Float.compare(that.yDpi, yDpi) == 0
                     && Float.compare(that.refreshRate, refreshRate) == 0
+                    && Float.compare(that.vsyncRate, vsyncRate) == 0
                     && appVsyncOffsetNanos == that.appVsyncOffsetNanos
                     && presentationDeadlineNanos == that.presentationDeadlineNanos
                     && Arrays.equals(supportedHdrTypes, that.supportedHdrTypes)
@@ -1835,8 +1843,9 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(id, width, height, xDpi, yDpi, refreshRate, appVsyncOffsetNanos,
-                    presentationDeadlineNanos, group, Arrays.hashCode(supportedHdrTypes));
+            return Objects.hash(id, width, height, xDpi, yDpi, refreshRate, vsyncRate,
+                    appVsyncOffsetNanos, presentationDeadlineNanos, group,
+                    Arrays.hashCode(supportedHdrTypes));
         }
     }
 
@@ -2785,10 +2794,22 @@
          * as a new transaction.
          */
         public void apply() {
-            apply(false);
+            apply(/*sync*/ false);
         }
 
         /**
+         * Applies the transaction as a one way binder call. This transaction will be applied out
+         * of order with other transactions that are applied synchronously. This method is not
+         * safe. It should only be used when the order does not matter.
+         *
+         * @hide
+         */
+        public void applyAsyncUnsafe() {
+            apply(/*sync*/ false, /*oneWay*/ true);
+        }
+
+
+        /**
          * Clear the transaction object, without applying it.
          *
          * @hide
@@ -2817,9 +2838,13 @@
          * @hide
          */
         public void apply(boolean sync) {
+            apply(sync, /*oneWay*/ false);
+        }
+
+        private void apply(boolean sync, boolean oneWay) {
             applyResizedSurfaces();
             notifyReparentedSurfaces();
-            nativeApplyTransaction(mNativeObject, sync);
+            nativeApplyTransaction(mNativeObject, sync, oneWay);
         }
 
         /**
@@ -3684,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.
          *
@@ -3692,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;
         }
 
@@ -4241,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.
@@ -4275,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
@@ -4373,7 +4400,7 @@
         void applyGlobalTransaction(boolean sync) {
             applyResizedSurfaces();
             notifyReparentedSurfaces();
-            nativeApplyTransaction(mNativeObject, sync);
+            nativeApplyTransaction(mNativeObject, sync, /*oneWay*/ false);
         }
 
         @Override
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cfde400..16318e0 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -12161,12 +12161,6 @@
      * a precision touch gesture in a small area in either the X or Y dimension, such as
      * an edge swipe or dragging a <code>SeekBar</code> thumb.</p>
      *
-     * <p>On Wear OS, these rects control where system-level swipe-to-dismiss gesture can start. If
-     * the attribute {@code android:windowSwipeToDismiss} has been set to {@code false}, the system
-     * will create an exclusion rect with size equal to the window frame size. In order words, the
-     * system swipe-to-dismiss will not apply, and the app must handle gestural input within itself.
-     * </p>
-     *
      * <p>Note: the system will put a limit of <code>200dp</code> on the vertical extent of the
      * exclusions it takes into account. The limit does not apply while the navigation
      * bar is {@link #SYSTEM_UI_FLAG_IMMERSIVE_STICKY stickily} hidden, nor to the
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index cf46bcc..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;
@@ -2232,9 +2232,7 @@
             mStopped = stopped;
             final ThreadedRenderer renderer = mAttachInfo.mThreadedRenderer;
             if (renderer != null) {
-                if (DEBUG_DRAW) {
-                    Log.d(mTag, "WindowStopped on " + getTitle() + " set to " + mStopped);
-                }
+                if (DEBUG_DRAW) Log.d(mTag, "WindowStopped on " + getTitle() + " set to " + mStopped);
                 renderer.setStopped(mStopped);
             }
             if (!mStopped) {
@@ -3351,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();
 
@@ -3879,8 +3877,8 @@
                 mPendingTransitions.clear();
             }
 
-            handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction,
-                    "view not visible");
+            handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions,
+                    mPendingTransaction, "view not visible");
         } else if (cancelAndRedraw) {
             mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
                 ? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason()
@@ -3895,8 +3893,8 @@
                 mPendingTransitions.clear();
             }
             if (!performDraw(mActiveSurfaceSyncGroup)) {
-                handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction,
-                        mLastPerformDrawSkippedReason);
+                handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions,
+                        mPendingTransaction, mLastPerformDrawSkippedReason);
             }
         }
 
@@ -4774,8 +4772,8 @@
             if (mSurfaceHolder != null && mSurface.isValid()) {
                 usingAsyncReport = true;
                 SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() -> {
-                    handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction,
-                            "SurfaceHolder");
+                    handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction != null,
+                            pendingTransaction, "SurfaceHolder");
                 });
 
                 SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
@@ -4789,8 +4787,8 @@
         }
 
         if (!usingAsyncReport) {
-            handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction,
-                    "no async report");
+            handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction != null,
+                    pendingTransaction, "no async report");
         }
 
         if (mPerformContentCapture) {
@@ -4800,13 +4798,14 @@
     }
 
     private void handleSyncRequestWhenNoAsyncDraw(SurfaceSyncGroup surfaceSyncGroup,
-            @Nullable Transaction pendingTransaction, String logReason) {
+            boolean hasPendingTransaction, @Nullable Transaction pendingTransaction,
+            String logReason) {
         if (surfaceSyncGroup != null) {
-            if (pendingTransaction != null) {
+            if (hasPendingTransaction && pendingTransaction != null) {
                 surfaceSyncGroup.addTransaction(pendingTransaction);
             }
             surfaceSyncGroup.markSyncReady();
-        } else if (pendingTransaction != null) {
+        } else if (hasPendingTransaction && pendingTransaction != null) {
             Trace.instant(Trace.TRACE_TAG_VIEW,
                     "Transaction not synced due to " + logReason + "-" + mTag);
             if (DEBUG_BLAST) {
@@ -5320,6 +5319,29 @@
     }
 
     /**
+     * Called from DecorView when gesture interception state has changed.
+     *
+     * @param intercepted If DecorView is intercepting touch events
+     */
+    public void updateDecorViewGestureInterception(boolean intercepted) {
+        mHandler.sendMessage(
+                mHandler.obtainMessage(
+                        MSG_DECOR_VIEW_GESTURE_INTERCEPTION,
+                        /* arg1= */ intercepted ? 1 : 0,
+                        /* arg2= */ 0));
+    }
+
+    void decorViewInterceptionChanged(boolean intercepted) {
+        if (mView != null) {
+            try {
+                mWindowSession.reportDecorViewGestureInterceptionChanged(mWindow, intercepted);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
      * Set the root-level system gesture exclusion rects. These are added to those provided by
      * the root's view hierarchy.
      */
@@ -5630,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;
         }
@@ -5644,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();
@@ -5942,6 +5968,7 @@
     private static final int MSG_KEEP_CLEAR_RECTS_CHANGED = 35;
     private static final int MSG_REPORT_KEEP_CLEAR_RECTS = 36;
     private static final int MSG_PAUSED_FOR_SYNC_TIMEOUT = 37;
+    private static final int MSG_DECOR_VIEW_GESTURE_INTERCEPTION = 38;
 
     final class ViewRootHandler extends Handler {
         @Override
@@ -6220,6 +6247,9 @@
                 case MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED: {
                     systemGestureExclusionChanged();
                 }   break;
+                case MSG_DECOR_VIEW_GESTURE_INTERCEPTION: {
+                    decorViewInterceptionChanged(/* intercepted= */ msg.arg1 == 1);
+                }   break;
                 case MSG_KEEP_CLEAR_RECTS_CHANGED: {
                     keepClearRectsChanged(/* accessibilityFocusRectChanged= */ msg.arg1 == 1);
                 }   break;
@@ -8579,10 +8609,6 @@
             mLastLayoutFrame.set(frame);
         }
 
-        if (mOnBackInvokedDispatcher.isSystemGestureExclusionNeeded()) {
-            setRootSystemGestureExclusionRects(List.of(frame));
-        }
-
         final WindowConfiguration winConfig = getCompatWindowConfiguration();
         mPendingBackDropFrame.set(mPendingDragResizing && !winConfig.useWindowFrameForBackdrop()
                 ? winConfig.getMaxBounds()
@@ -9027,8 +9053,8 @@
             mAdded = false;
             AnimationHandler.removeRequestor(this);
         }
-        handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction,
-                "shutting down VRI");
+        handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions,
+                mPendingTransaction, "shutting down VRI");
         WindowManagerGlobal.getInstance().doRemoveView(this);
     }
 
@@ -10521,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 2f4bea0..a3b93b4 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -82,6 +82,8 @@
 
 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;
@@ -1683,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.
@@ -1691,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) {
@@ -3113,7 +3115,7 @@
         /**
          * Never animate position changes of the window.
          *
-         * @see android.R.attr#Window_windowNoMoveAnimation
+         * @see android.R.styleable#Window_windowNoMoveAnimation
          * {@hide}
          */
         @UnsupportedAppUsage
@@ -4314,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.
@@ -4526,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) {
@@ -4541,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;
@@ -4716,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
@@ -4865,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
@@ -4936,6 +4975,7 @@
             forciblyShownTypes = in.readInt();
             paramsForRotation = in.createTypedArray(LayoutParams.CREATOR);
             mDisplayFlags = in.readInt();
+            mDesiredHdrHeadroom = in.readFloat();
         }
 
         @SuppressWarnings({"PointlessBitwiseExpression"})
@@ -5196,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;
@@ -5423,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);
@@ -5821,6 +5869,7 @@
      *
      * @hide
      */
+    @FlaggedApi("REPLACE_CONTENT_WITH_MIRROR")
     @TestApi
     @RequiresPermission(permission.ACCESS_SURFACE_FLINGER)
     default boolean replaceContentOnDisplayWithMirror(int displayId, @NonNull Window window) {
@@ -5836,6 +5885,7 @@
      *
      * @hide
      */
+    @FlaggedApi("REPLACE_CONTENT_WITH_MIRROR")
     @TestApi
     @RequiresPermission(permission.ACCESS_SURFACE_FLINGER)
     default boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) {
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/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 8fe9b7b..7c3b6ae 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -584,9 +584,13 @@
     }
 
     @Override
-    public void reportKeepClearAreasChanged(android.view.IWindow window, List<Rect> restrictedRects,
-            List<Rect> unrestrictedRects) {
-    }
+    public void reportDecorViewGestureInterceptionChanged(IWindow window, boolean intercepted) {}
+
+    @Override
+    public void reportKeepClearAreasChanged(
+            android.view.IWindow window,
+            List<Rect> restrictedRects,
+            List<Rect> unrestrictedRects) {}
 
     @Override
     public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window,
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/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index e31ad82..85dadd4 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -5,4 +5,11 @@
     name: "force_invert_color"
     description: "Enable force force-dark for smart inversion and dark theme everywhere"
     bug: "282821643"
-}
\ No newline at end of file
+}
+
+flag {
+    namespace: "accessibility"
+    name: "allow_shortcut_chooser_on_lockscreen"
+    description: "Allows the a11y shortcut disambig dialog to appear on the lockscreen"
+    bug: "303871725"
+}
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index a07b62f..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 .
      *
@@ -108,11 +108,14 @@
      * @hide
      */
     @TestApi
+    @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
     public static void lockAnimationClock(long vsyncMillis, long expectedPresentationTimeNanos) {
         AnimationState state = sAnimationState.get();
         state.animationClockLocked = true;
         state.currentVsyncTimeMillis = vsyncMillis;
-        state.mExpectedPresentationTimeNanos = expectedPresentationTimeNanos;
+        if (!expectedPresentationTimeApi()) {
+            state.mExpectedPresentationTimeNanos = expectedPresentationTimeNanos;
+        }
     }
 
     /**
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 89fa83e..a40ff64 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -16,7 +16,6 @@
 
 package android.view.autofill;
 
-import static android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS;
 import static android.service.autofill.FillRequest.FLAG_IME_SHOWING;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
 import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
@@ -31,12 +30,10 @@
 import static android.view.autofill.Helper.toList;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
-import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
-import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -53,20 +50,17 @@
 import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
 import android.metrics.LogMaker;
-import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.ICancellationSignal;
 import android.os.Looper;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.service.autofill.AutofillService;
-import android.service.autofill.FillCallback;
 import android.service.autofill.FillEventHistory;
+import android.service.autofill.Flags;
 import android.service.autofill.IFillCallback;
 import android.service.autofill.UserData;
 import android.text.TextUtils;
@@ -88,7 +82,6 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
 import android.view.accessibility.AccessibilityWindowInfo;
-import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.CheckBox;
 import android.widget.DatePicker;
@@ -118,7 +111,6 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
-import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import sun.misc.Cleaner;
@@ -188,12 +180,6 @@
  * shows an autofill save UI if the value of savable views have changed. If the user selects the
  * option to Save, the current value of the views is then sent to the autofill service.
  *
- * <p>There is another choice for the application to provide it's datasets to the Autofill framework
- * by setting an {@link AutofillRequestCallback} through
- * {@link #setAutofillRequestCallback(Executor, AutofillRequestCallback)}. The application can use
- * its callback instead of the default {@link AutofillService}. See
- * {@link AutofillRequestCallback} for more details.
- *
  * <h3 id="additional-notes">Additional notes</h3>
  *
  * <p>It is safe to call <code>AutofillManager</code> methods from any thread.
@@ -277,6 +263,12 @@
             "android.view.autofill.extra.CLIENT_STATE";
 
     /**
+     * @hide
+     */
+    public static final String EXTRA_AUTH_STATE =
+            "android.view.autofill.extra.AUTH_STATE";
+
+    /**
      * Intent extra: the {@link android.view.inputmethod.InlineSuggestionsRequest} in the
      * autofill request.
      *
@@ -327,7 +319,6 @@
     /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2;
     /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4;
     /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY = 0x8;
-    /** @hide */ public static final int FLAG_ENABLED_CLIENT_SUGGESTIONS = 0x20;
 
     // NOTE: flag below is used by the session start receiver only, hence it can have values above
     /** @hide */ public static final int RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY = 0x1;
@@ -428,6 +419,14 @@
     public static final int STATE_UNKNOWN_FAILED = 6;
 
     /**
+     * Same as {@link #STATE_ACTIVE}, but when pending authentication after
+     * {@link AutofillManagerClient#authenticate(int, int, IntentSender, Intent, boolean)}
+     *
+     * @hide
+     */
+    public static final int STATE_PENDING_AUTHENTICATION = 7;
+
+    /**
      * Timeout in ms for calls to the field classification service.
      * @hide
      */
@@ -661,11 +660,6 @@
     @GuardedBy("mLock")
     private boolean mEnabledForAugmentedAutofillOnly;
 
-    @GuardedBy("mLock")
-    @Nullable private AutofillRequestCallback mAutofillRequestCallback;
-    @GuardedBy("mLock")
-    @Nullable private Executor mRequestCallbackExecutor;
-
     private boolean mScreenHasCredmanField;
 
     /**
@@ -731,6 +725,10 @@
     // Indicate whether WebView should always be included in the assist structure
     private boolean mShouldAlwaysIncludeWebviewInAssistStructure;
 
+    // Controls logic around apps changing some properties of their views when activity loses
+    // focus due to autofill showing biometric activity, password manager, or password breach check.
+    private boolean mRelayoutFix;
+
     // Indicates whether called the showAutofillDialog() method.
     private boolean mShowAutofillDialogCalled = false;
 
@@ -952,6 +950,8 @@
 
         mShouldAlwaysIncludeWebviewInAssistStructure =
                 AutofillFeatureFlags.shouldAlwaysIncludeWebviewInAssistStructure();
+
+        mRelayoutFix = Flags.relayout();
     }
 
     /**
@@ -1715,7 +1715,13 @@
         synchronized (mLock) {
             if (mForAugmentedAutofillOnly) {
                 if (sVerbose) {
-                    Log.v(TAG,  "notifyViewVisibilityChanged(): ignoring on augmented only mode");
+                    Log.v(TAG, "notifyViewVisibilityChanged(): ignoring on augmented only mode");
+                }
+                return;
+            }
+            if (mRelayoutFix && mState == STATE_PENDING_AUTHENTICATION) {
+                if (sVerbose) {
+                    Log.v(TAG, "notifyViewVisibilityChanged(): ignoring in auth pending mode");
                 }
                 return;
             }
@@ -2342,6 +2348,7 @@
             if (!isActiveLocked()) {
                 return;
             }
+            mState = STATE_ACTIVE;
             // If authenticate activity closes itself during onCreate(), there is no onStop/onStart
             // of app activity.  We enforce enter event to re-show fill ui in such case.
             // CTS example:
@@ -2406,38 +2413,6 @@
         return new AutofillId(parent.getAutofillViewId(), virtualId);
     }
 
-    /**
-     * Sets the client's suggestions callback for autofill.
-     *
-     * @see AutofillRequestCallback
-     *
-     * @param executor specifies the thread upon which the callbacks will be invoked.
-     * @param callback which handles autofill request to provide client's suggestions.
-     */
-    @RequiresPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
-    public void setAutofillRequestCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull AutofillRequestCallback callback) {
-        if (mContext.checkSelfPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires PROVIDE_OWN_AUTOFILL_SUGGESTIONS permission!");
-        }
-
-        synchronized (mLock) {
-            mRequestCallbackExecutor = executor;
-            mAutofillRequestCallback = callback;
-        }
-    }
-
-    /**
-     * clears the client's suggestions callback for autofill.
-     */
-    public void clearAutofillRequestCallback() {
-        synchronized (mLock) {
-            mRequestCallbackExecutor = null;
-            mAutofillRequestCallback = null;
-        }
-    }
-
     @GuardedBy("mLock")
     private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds,
             @NonNull AutofillValue value, int flags) {
@@ -2498,13 +2473,6 @@
                 }
             }
 
-            if (mAutofillRequestCallback != null) {
-                if (sDebug) {
-                    Log.d(TAG, "startSession with the client suggestions provider");
-                }
-                flags |= FLAG_ENABLED_CLIENT_SUGGESTIONS;
-            }
-
             mService.startSession(client.autofillClientGetActivityToken(),
                     mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
                     mCallback != null, flags, clientActivity,
@@ -2830,6 +2798,9 @@
             Intent fillInIntent, boolean authenticateInline) {
         synchronized (mLock) {
             if (sessionId == mSessionId) {
+                if (mRelayoutFix) {
+                    mState = STATE_PENDING_AUTHENTICATION;
+                }
                 final AutofillClient client = getClient();
                 if (client != null) {
                     // clear mOnInvisibleCalled and we will see if receive onInvisibleForAutofill()
@@ -2859,28 +2830,6 @@
         }
     }
 
-    private void onFillRequest(InlineSuggestionsRequest request,
-            CancellationSignal cancellationSignal, FillCallback callback) {
-        final AutofillRequestCallback autofillRequestCallback;
-        final Executor executor;
-        synchronized (mLock) {
-            autofillRequestCallback = mAutofillRequestCallback;
-            executor = mRequestCallbackExecutor;
-        }
-        if (autofillRequestCallback != null && executor != null) {
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                executor.execute(() ->
-                        autofillRequestCallback.onFillRequest(
-                                request, cancellationSignal, callback));
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        } else {
-            callback.onSuccess(null);
-        }
-    }
-
     /** @hide */
     public static final int SET_STATE_FLAG_ENABLED = 0x01;
     /** @hide */
@@ -3563,6 +3512,8 @@
                 return "UNKNOWN";
             case STATE_ACTIVE:
                 return "ACTIVE";
+            case STATE_PENDING_AUTHENTICATION:
+                return "PENDING_AUTHENTICATION";
             case STATE_FINISHED:
                 return "FINISHED";
             case STATE_SHOWING_SAVE_UI:
@@ -3592,7 +3543,12 @@
 
     @GuardedBy("mLock")
     private boolean isActiveLocked() {
-        return mState == STATE_ACTIVE;
+        return mState == STATE_ACTIVE || isPendingAuthenticationLocked();
+    }
+
+    @GuardedBy("mLock")
+    private boolean isPendingAuthenticationLocked() {
+        return mRelayoutFix && mState == STATE_PENDING_AUTHENTICATION;
     }
 
     @GuardedBy("mLock")
@@ -4457,23 +4413,6 @@
         }
 
         @Override
-        public void requestFillFromClient(int id, InlineSuggestionsRequest request,
-                IFillCallback callback) {
-            final AutofillManager afm = mAfm.get();
-            if (afm != null) {
-                ICancellationSignal transport = CancellationSignal.createTransport();
-                try {
-                    callback.onCancellable(transport);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Error requesting a cancellation", e);
-                }
-
-                afm.onFillRequest(request, CancellationSignal.fromTransport(transport),
-                        new FillCallback(callback, id));
-            }
-        }
-
-        @Override
         public void notifyFillDialogTriggerIds(List<AutofillId> ids) {
             final AutofillManager afm = mAfm.get();
             if (afm != null) {
diff --git a/core/java/android/view/autofill/AutofillRequestCallback.java b/core/java/android/view/autofill/AutofillRequestCallback.java
deleted file mode 100644
index e632a58..0000000
--- a/core/java/android/view/autofill/AutofillRequestCallback.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.autofill;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.CancellationSignal;
-import android.service.autofill.FillCallback;
-import android.view.inputmethod.InlineSuggestionsRequest;
-
-/**
- * <p>This class is used to provide some input suggestions to the Autofill framework.
- *
- * <P>When the user is requested to input something, Autofill will try to query input suggestions
- * for the user choosing. If the application want to provide some internal input suggestions,
- * implements this callback and register via
- * {@link AutofillManager#setAutofillRequestCallback(java.util.concurrent.Executor,
- * AutofillRequestCallback)}. Autofill will callback the
- * {@link #onFillRequest(InlineSuggestionsRequest, CancellationSignal, FillCallback)} to request
- * input suggestions.
- *
- * <P>To make sure the callback to take effect, must register before the autofill session starts.
- * If the autofill session is started, calls {@link AutofillManager#cancel()} to finish current
- * session, and then the callback will be used at the next restarted session.
- *
- * <P>To create a {@link android.service.autofill.FillResponse}, application should fetch
- * {@link AutofillId}s from its view structure. Below is an example:
- * <pre class="prettyprint">
- * AutofillId usernameId = findViewById(R.id.username).getAutofillId();
- * AutofillId passwordId = findViewById(R.id.password).getAutofillId();
- * </pre>
- * To learn more about creating a {@link android.service.autofill.FillResponse}, read
- * <a href="/guide/topics/text/autofill-services#fill">Fill out client views</a>.
- *
- * <P>To fallback to the default {@link android.service.autofill.AutofillService}, just respond
- * a null of the {@link android.service.autofill.FillResponse}. And then Autofill will do a fill
- * request with the default {@link android.service.autofill.AutofillService}. Or clear the callback
- * from {@link AutofillManager} via {@link AutofillManager#clearAutofillRequestCallback()}. If the
- * client would like to keep no suggestions for the field, respond with an empty
- * {@link android.service.autofill.FillResponse} which has no dataset.
- *
- * <P>IMPORTANT: This should not be used for displaying anything other than input suggestions, or
- * the keyboard may choose to block your app from the inline strip.
- */
-public interface AutofillRequestCallback {
-    /**
-     * Called by the Android system to decide if a screen can be autofilled by the callback.
-     *
-     * @param inlineSuggestionsRequest the {@link InlineSuggestionsRequest request} to handle if
-     *     currently inline suggestions are supported and can be displayed.
-     * @param cancellationSignal signal for observing cancellation requests. The system will use
-     *     this to notify you that the fill result is no longer needed and you should stop
-     *     handling this fill request in order to save resources.
-     * @param callback object used to notify the result of the request.
-     */
-    void onFillRequest(@Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
-            @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback);
-}
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 6e13097..917a974 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -24,11 +24,9 @@
 import android.content.IntentSender;
 import android.graphics.Rect;
 import android.os.IBinder;
-import android.service.autofill.IFillCallback;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutofillWindowPresenter;
-import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.KeyEvent;
 
 import com.android.internal.os.IResultReceiver;
@@ -149,12 +147,6 @@
    void requestShowSoftInput(in AutofillId id);
 
     /**
-     * Requests to determine if a screen can be autofilled by the client app.
-     */
-    void requestFillFromClient(int id, in InlineSuggestionsRequest request,
-            in IFillCallback callback);
-
-    /**
      * Notifies autofill ids that require to show the fill dialog.
      */
     void notifyFillDialogTriggerIds(in List<AutofillId> ids);
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/flags/variable_refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
similarity index 78%
rename from core/java/android/view/flags/variable_refresh_rate_flags.aconfig
rename to core/java/android/view/flags/refresh_rate_flags.aconfig
index 13a6f8d..56b5fac 100644
--- a/core/java/android/view/flags/variable_refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -19,4 +19,11 @@
     namespace: "toolkit"
     description: "Feature flag for using expected presentation time of the Choreographer"
     bug: "278730197"
+}
+
+flag {
+  name: "set_frame_rate_callback"
+  namespace: "core_graphics"
+  description: "Enable the `setFrameRate` callback"
+  bug: "299946220"
 }
\ No newline at end of file
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/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index 70279cc..c83dfe8 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -111,22 +111,6 @@
     private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec;
 
     /**
-     * Whether the IME supports inline suggestions from the default Autofill service that
-     * provides the input view.
-     *
-     * Note: The default value is {@code true}.
-     */
-    private boolean mServiceSupported;
-
-    /**
-     * Whether the IME supports inline suggestions from the application that provides the
-     * input view.
-     *
-     * Note: The default value is {@code true}.
-     */
-    private boolean mClientSupported;
-
-    /**
      * @hide
      * @see {@link #mHostInputToken}.
      */
@@ -220,14 +204,6 @@
         return Bundle.EMPTY;
     }
 
-    private static boolean defaultServiceSupported() {
-        return true;
-    }
-
-    private static boolean defaultClientSupported() {
-        return true;
-    }
-
     /** @hide */
     abstract static class BaseBuilder {
         abstract Builder setInlinePresentationSpecs(
@@ -240,25 +216,13 @@
         abstract Builder setHostDisplayId(int value);
     }
 
-    /** @hide */
-    public boolean isServiceSupported() {
-        return mServiceSupported;
-    }
-
-    /** @hide */
-    public boolean isClientSupported() {
-        return mClientSupported;
-    }
-
-
-
-    // Code below generated by codegen v1.0.22.
+    // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+    // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -274,9 +238,7 @@
             @NonNull Bundle extras,
             @Nullable IBinder hostInputToken,
             int hostDisplayId,
-            @Nullable InlinePresentationSpec inlineTooltipPresentationSpec,
-            boolean serviceSupported,
-            boolean clientSupported) {
+            @Nullable InlinePresentationSpec inlineTooltipPresentationSpec) {
         this.mMaxSuggestionCount = maxSuggestionCount;
         this.mInlinePresentationSpecs = inlinePresentationSpecs;
         com.android.internal.util.AnnotationValidations.validate(
@@ -293,8 +255,6 @@
         this.mHostInputToken = hostInputToken;
         this.mHostDisplayId = hostDisplayId;
         this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec;
-        this.mServiceSupported = serviceSupported;
-        this.mClientSupported = clientSupported;
 
         onConstructed();
     }
@@ -378,9 +338,7 @@
     }
 
     /**
-     * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response.
-     *
-     * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)
+     * Specifies the UI specification for the inline suggestion tooltip in the response.
      */
     @DataClass.Generated.Member
     public @Nullable InlinePresentationSpec getInlineTooltipPresentationSpec() {
@@ -401,9 +359,7 @@
                 "extras = " + mExtras + ", " +
                 "hostInputToken = " + mHostInputToken + ", " +
                 "hostDisplayId = " + mHostDisplayId + ", " +
-                "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec + ", " +
-                "serviceSupported = " + mServiceSupported + ", " +
-                "clientSupported = " + mClientSupported +
+                "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec +
         " }";
     }
 
@@ -427,9 +383,7 @@
                 && extrasEquals(that.mExtras)
                 && java.util.Objects.equals(mHostInputToken, that.mHostInputToken)
                 && mHostDisplayId == that.mHostDisplayId
-                && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec)
-                && mServiceSupported == that.mServiceSupported
-                && mClientSupported == that.mClientSupported;
+                && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec);
     }
 
     @Override
@@ -447,8 +401,6 @@
         _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
         _hash = 31 * _hash + mHostDisplayId;
         _hash = 31 * _hash + java.util.Objects.hashCode(mInlineTooltipPresentationSpec);
-        _hash = 31 * _hash + Boolean.hashCode(mServiceSupported);
-        _hash = 31 * _hash + Boolean.hashCode(mClientSupported);
         return _hash;
     }
 
@@ -459,8 +411,6 @@
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
         int flg = 0;
-        if (mServiceSupported) flg |= 0x100;
-        if (mClientSupported) flg |= 0x200;
         if (mHostInputToken != null) flg |= 0x20;
         if (mInlineTooltipPresentationSpec != null) flg |= 0x80;
         dest.writeInt(flg);
@@ -486,11 +436,9 @@
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
         int flg = in.readInt();
-        boolean serviceSupported = (flg & 0x100) != 0;
-        boolean clientSupported = (flg & 0x200) != 0;
         int maxSuggestionCount = in.readInt();
         List<InlinePresentationSpec> inlinePresentationSpecs = new ArrayList<>();
-        in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader(), android.widget.inline.InlinePresentationSpec.class);
+        in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader());
         String hostPackageName = in.readString();
         LocaleList supportedLocales = (LocaleList) in.readTypedObject(LocaleList.CREATOR);
         Bundle extras = in.readBundle();
@@ -514,8 +462,6 @@
         this.mHostInputToken = hostInputToken;
         this.mHostDisplayId = hostDisplayId;
         this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec;
-        this.mServiceSupported = serviceSupported;
-        this.mClientSupported = clientSupported;
 
         onConstructed();
     }
@@ -549,8 +495,6 @@
         private @Nullable IBinder mHostInputToken;
         private int mHostDisplayId;
         private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec;
-        private boolean mServiceSupported;
-        private boolean mClientSupported;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -683,9 +627,7 @@
         }
 
         /**
-         * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response.
-         *
-         * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)s
+         * Specifies the UI specification for the inline suggestion tooltip in the response.
          */
         @DataClass.Generated.Member
         public @NonNull Builder setInlineTooltipPresentationSpec(@NonNull InlinePresentationSpec value) {
@@ -695,38 +637,10 @@
             return this;
         }
 
-        /**
-         * Whether the IME supports inline suggestions from the default Autofill service that
-         * provides the input view.
-         *
-         * Note: The default value is {@code true}.
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setServiceSupported(boolean value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x100;
-            mServiceSupported = value;
-            return this;
-        }
-
-        /**
-         * Whether the IME supports inline suggestions from the application that provides the
-         * input view.
-         *
-         * Note: The default value is {@code true}.
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setClientSupported(boolean value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x200;
-            mClientSupported = value;
-            return this;
-        }
-
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull InlineSuggestionsRequest build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x400; // Mark builder used
+            mBuilderFieldsSet |= 0x100; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mMaxSuggestionCount = defaultMaxSuggestionCount();
@@ -749,12 +663,6 @@
             if ((mBuilderFieldsSet & 0x80) == 0) {
                 mInlineTooltipPresentationSpec = defaultInlineTooltipPresentationSpec();
             }
-            if ((mBuilderFieldsSet & 0x100) == 0) {
-                mServiceSupported = defaultServiceSupported();
-            }
-            if ((mBuilderFieldsSet & 0x200) == 0) {
-                mClientSupported = defaultClientSupported();
-            }
             InlineSuggestionsRequest o = new InlineSuggestionsRequest(
                     mMaxSuggestionCount,
                     mInlinePresentationSpecs,
@@ -763,14 +671,12 @@
                     mExtras,
                     mHostInputToken,
                     mHostDisplayId,
-                    mInlineTooltipPresentationSpec,
-                    mServiceSupported,
-                    mClientSupported);
+                    mInlineTooltipPresentationSpec);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x400) != 0) {
+            if ((mBuilderFieldsSet & 0x100) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -778,10 +684,10 @@
     }
 
     @DataClass.Generated(
-            time = 1615798784918L,
-            codegenVersion = "1.0.22",
+            time = 1696889841006L,
+            codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java",
-            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate  int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate  boolean mServiceSupported\nprivate  boolean mClientSupported\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic  void setHostInputToken(android.os.IBinder)\nprivate  boolean extrasEquals(android.os.Bundle)\nprivate  void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic  void setHostDisplayId(int)\nprivate  void onConstructed()\npublic  void filterContentTypes()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nprivate static  android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static  android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nprivate static  boolean defaultServiceSupported()\nprivate static  boolean defaultClientSupported()\npublic  boolean isServiceSupported()\npublic  boolean isClientSupported()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate  int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic  void setHostInputToken(android.os.IBinder)\nprivate  boolean extrasEquals(android.os.Bundle)\nprivate  void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic  void setHostDisplayId(int)\nprivate  void onConstructed()\npublic  void filterContentTypes()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nprivate static  android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static  android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
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/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 26ceea6..1fdd1a5 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -80,6 +80,7 @@
 import android.view.autofill.AutofillId;
 import android.view.contentcapture.ContentCaptureManager;
 import android.view.contentcapture.ContentCaptureSession;
+import android.view.flags.Flags;
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
@@ -92,7 +93,6 @@
 import android.view.inputmethod.SurroundingText;
 import android.view.inspector.InspectableProperty;
 import android.view.inspector.InspectableProperty.EnumEntry;
-import android.widget.flags.Flags;
 import android.widget.RemoteViews.InteractionHandler;
 
 import com.android.internal.R;
@@ -4518,7 +4518,7 @@
                     final int overscrollMode = getOverScrollMode();
 
                     if (!trackMotionScroll(delta, delta)) {
-                        if (Flags.platformWidgetHapticScrollFeedback()) {
+                        if (Flags.scrollFeedbackApi()) {
                             initHapticScrollFeedbackProviderIfNotExists();
                             mHapticScrollFeedbackProvider.onScrollProgress(
                                     event.getDeviceId(), event.getSource(), axis, delta);
@@ -4534,7 +4534,7 @@
                         float overscroll = (delta - (motionViewRealTop - motionViewPrevTop))
                                 / ((float) getHeight());
                         boolean hitTopLimit = delta > 0;
-                        if (Flags.platformWidgetHapticScrollFeedback()) {
+                        if (Flags.scrollFeedbackApi()) {
                             initHapticScrollFeedbackProviderIfNotExists();
                             mHapticScrollFeedbackProvider.onScrollLimit(
                                     event.getDeviceId(), event.getSource(), axis,
diff --git a/core/java/android/widget/DifferentialMotionFlingHelper.java b/core/java/android/widget/DifferentialMotionFlingHelper.java
index 95d24ec..ef01c3b 100644
--- a/core/java/android/widget/DifferentialMotionFlingHelper.java
+++ b/core/java/android/widget/DifferentialMotionFlingHelper.java
@@ -21,6 +21,8 @@
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
+import android.widget.flags.FeatureFlags;
+import android.widget.flags.FeatureFlagsImpl;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -50,6 +52,8 @@
     private final FlingVelocityThresholdCalculator mVelocityThresholdCalculator;
     private final DifferentialVelocityProvider mVelocityProvider;
 
+    private final FeatureFlags mWidgetFeatureFlags;
+
     @Nullable private VelocityTracker mVelocityTracker;
 
     private float mLastFlingVelocity;
@@ -134,7 +138,8 @@
         this(context,
                 target,
                 DifferentialMotionFlingHelper::calculateFlingVelocityThresholds,
-                DifferentialMotionFlingHelper::getCurrentVelocity);
+                DifferentialMotionFlingHelper::getCurrentVelocity,
+                /* widgetFeatureFlags= */ new FeatureFlagsImpl());
     }
 
     @VisibleForTesting
@@ -142,11 +147,13 @@
             Context context,
             DifferentialMotionFlingTarget target,
             FlingVelocityThresholdCalculator velocityThresholdCalculator,
-            DifferentialVelocityProvider velocityProvider) {
+            DifferentialVelocityProvider velocityProvider,
+            FeatureFlags widgetFeatureFlags) {
         mContext = context;
         mTarget = target;
         mVelocityThresholdCalculator = velocityThresholdCalculator;
         mVelocityProvider = velocityProvider;
+        mWidgetFeatureFlags = widgetFeatureFlags;
     }
 
     /**
@@ -156,6 +163,9 @@
      * @param axis the axis being processed by the target View.
      */
     public void onMotionEvent(MotionEvent event, int axis) {
+        if (!mWidgetFeatureFlags.enablePlatformWidgetDifferentialMotionFling()) {
+            return;
+        }
         boolean flingParamsChanged = calculateFlingVelocityThresholds(event, axis);
         if (mFlingVelocityThresholds[0] == Integer.MAX_VALUE) {
             // Integer.MAX_VALUE means that the device does not support fling for the current
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index e0e72ba..a1ebde7 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -47,8 +47,8 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.AnimationUtils;
+import android.view.flags.Flags;
 import android.view.inspector.InspectableProperty;
-import android.widget.flags.Flags;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -1011,14 +1011,14 @@
                     if (newScrollY != oldScrollY) {
                         super.scrollTo(mScrollX, newScrollY);
                         if (hitLimit) {
-                            if (Flags.platformWidgetHapticScrollFeedback()) {
+                            if (Flags.scrollFeedbackApi()) {
                                 initHapticScrollFeedbackProviderIfNotExists();
                                 mHapticScrollFeedbackProvider.onScrollLimit(
                                         event.getDeviceId(), event.getSource(), axis,
                                         /* isStart= */ newScrollY == 0);
                             }
                         } else {
-                            if (Flags.platformWidgetHapticScrollFeedback()) {
+                            if (Flags.scrollFeedbackApi()) {
                                 initHapticScrollFeedbackProviderIfNotExists();
                                 mHapticScrollFeedbackProvider.onScrollProgress(
                                         event.getDeviceId(), event.getSource(), axis, delta);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 2c41330..e8281ea 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -567,6 +567,8 @@
     private float mShadowDy;
     private int mShadowColor;
 
+    private int mLastOrientation;
+
     private boolean mPreDrawRegistered;
     private boolean mPreDrawListenerDetached;
 
@@ -1193,6 +1195,7 @@
         mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
         mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
         mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
+        mLastOrientation = getResources().getConfiguration().orientation;
 
         final Resources.Theme theme = context.getTheme();
 
@@ -4591,6 +4594,15 @@
             mFontWeightAdjustment = newConfig.fontWeightAdjustment;
             setTypeface(getTypeface());
         }
+
+        InputMethodManager imm = getInputMethodManager();
+        // if orientation changed and this TextView is currently served.
+        if (mLastOrientation != newConfig.orientation
+                && imm != null && imm.hasActiveInputConnection(this)) {
+            // EditorInfo.internalImeOptions are out of date.
+            imm.restartInput(this);
+        }
+        mLastOrientation = newConfig.orientation;
     }
 
     /**
diff --git a/core/java/android/widget/flags/differential_motion_fling_flags.aconfig b/core/java/android/widget/flags/differential_motion_fling_flags.aconfig
new file mode 100644
index 0000000..79cfe56
--- /dev/null
+++ b/core/java/android/widget/flags/differential_motion_fling_flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.widget.flags"
+
+flag {
+    namespace: "toolkit"
+    name: "enable_platform_widget_differential_motion_fling"
+    description: "Enables differential motion fling in platform widgets"
+    bug: "293332089"
+}
\ No newline at end of file
diff --git a/core/java/android/widget/flags/scroll_view_flags.aconfig b/core/java/android/widget/flags/scroll_view_flags.aconfig
deleted file mode 100644
index f93ade2..0000000
--- a/core/java/android/widget/flags/scroll_view_flags.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "android.widget.flags"
-
-flag {
-    namespace: "widget"
-    name: "platform_widget_haptic_scroll_feedback"
-    description: "Enables haptic scroll feedback in platform widgets"
-    bug: "287914819"
-}
\ No newline at end of file
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index f53737a..5562360 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -49,7 +49,7 @@
     private final int mSwipeEdge;
 
     /**
-     * Creates a new {@link BackMotionEvent} instance.
+     * Creates a new {@link BackEvent} instance.
      *
      * @param touchX Absolute X location of the touch point of this event.
      * @param touchY Absolute Y location of the touch point of this event.
diff --git a/core/java/android/window/IRemoteTransition.aidl b/core/java/android/window/IRemoteTransition.aidl
index 2efb68a..ec8b66d 100644
--- a/core/java/android/window/IRemoteTransition.aidl
+++ b/core/java/android/window/IRemoteTransition.aidl
@@ -59,4 +59,12 @@
     void mergeAnimation(in IBinder transition, in TransitionInfo info,
             in SurfaceControl.Transaction t, in IBinder mergeTarget,
             in IRemoteTransitionFinishedCallback finishCallback);
+
+    /**
+     * Called when a different handler has consumed the transition
+     *
+     * @param transition An identifier for the transition that was consumed.
+     * @param aborted Whether the transition is aborted or not.
+     */
+    void onTransitionConsumed(in IBinder transition, in boolean aborted);
 }
diff --git a/core/java/android/window/SystemPerformanceHinter.java b/core/java/android/window/SystemPerformanceHinter.java
index b2c977b..2736b68 100644
--- a/core/java/android/window/SystemPerformanceHinter.java
+++ b/core/java/android/window/SystemPerformanceHinter.java
@@ -17,6 +17,8 @@
 package android.window;
 
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
 import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN;
 import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF;
 
@@ -29,8 +31,6 @@
 import android.util.Log;
 import android.view.SurfaceControl;
 
-import com.android.internal.annotations.VisibleForTesting;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Random;
@@ -148,7 +148,6 @@
      * Constructor for the hinter.
      * @hide
      */
-    @VisibleForTesting
     public SystemPerformanceHinter(@NonNull Context context,
             @Nullable DisplayRootProvider displayRootProvider,
             @Nullable Supplier<SurfaceControl.Transaction> transactionSupplier) {
@@ -208,11 +207,18 @@
         boolean transactionChanged = false;
         // Per-display flags
         if (nowEnabled(oldPerDisplayFlags, newPerDisplayFlags, HINT_SF_FRAME_RATE)) {
-            mTransaction.setFrameRateSelectionStrategy(
-                    mDisplayRootProvider.getRootForDisplay(session.displayId),
+            SurfaceControl displaySurfaceControl = mDisplayRootProvider.getRootForDisplay(
+                    session.displayId);
+            mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
                     FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
+            // 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.reason, session.traceCookie);
+            Trace.beginAsyncSection("PerfHint-framerate-" + session.displayId + "-"
+                    + session.reason, session.traceCookie);
         }
 
         // Global flags
@@ -226,7 +232,7 @@
             Trace.beginAsyncSection("PerfHint-adpf-" + session.reason, session.traceCookie);
         }
         if (transactionChanged) {
-            mTransaction.apply();
+            mTransaction.applyAsyncUnsafe();
         }
     }
 
@@ -245,25 +251,32 @@
         boolean transactionChanged = false;
         // Per-display flags
         if (nowDisabled(oldPerDisplayFlags, newPerDisplayFlags, HINT_SF_FRAME_RATE)) {
-            mTransaction.setFrameRateSelectionStrategy(
-                    mDisplayRootProvider.getRootForDisplay(session.displayId),
+            SurfaceControl displaySurfaceControl = mDisplayRootProvider.getRootForDisplay(
+                    session.displayId);
+            mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
                     FRAME_RATE_SELECTION_STRATEGY_SELF);
+            // 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.reason, session.traceCookie);
+            Trace.endAsyncSection("PerfHint-framerate-" + session.displayId + "-" + session.reason,
+                    session.traceCookie);
         }
 
         // Global flags
         if (nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_SF_EARLY_WAKEUP)) {
             mTransaction.setEarlyWakeupEnd();
             transactionChanged = true;
-            Trace.endAsyncSection("PerfHint-early_wakeup" + session.reason, session.traceCookie);
+            Trace.endAsyncSection("PerfHint-early_wakeup-" + session.reason, session.traceCookie);
         }
         if (nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) {
             mAdpfSession.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
             Trace.endAsyncSection("PerfHint-adpf-" + session.reason, session.traceCookie);
         }
         if (transactionChanged) {
-            mTransaction.apply();
+            mTransaction.applyAsyncUnsafe();
         }
     }
 
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/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 0ee07bb..3d4bc2f 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -16,9 +16,6 @@
 
 package android.window;
 
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -37,8 +34,8 @@
 
 import androidx.annotation.VisibleForTesting;
 
+
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -60,18 +57,6 @@
  * @hide
  */
 public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
-    @Retention(SOURCE)
-    @IntDef({
-        BACK_CALLBACK_ENABLED,
-        BACK_CALLBACK_DISABLED,
-        BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS
-    })
-    public @interface OnBackInvokedCallbackType {}
-
-    public static final int BACK_CALLBACK_ENABLED = 0;
-    public static final int BACK_CALLBACK_DISABLED = 1;
-    public static final int BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS = 2;
-
     private IWindowSession mWindowSession;
     private IWindow mWindow;
     private static final String TAG = "WindowOnBackDispatcher";
@@ -283,6 +268,13 @@
     }
 
     /**
+     * Returns false if the legacy back behavior should be used.
+     */
+    public boolean isOnBackInvokedCallbackEnabled() {
+        return Checker.isOnBackInvokedCallbackEnabled(mChecker.getContext());
+    }
+
+    /**
      * Dump information about this WindowOnBackInvokedDispatcher
      * @param prefix the prefix that will be prepended to each line of the produced output
      * @param writer the writer that will receive the resulting text
@@ -395,20 +387,6 @@
         }
     }
 
-    /** Returns false if the legacy back behavior should be used. */
-    public boolean isOnBackInvokedCallbackEnabled() {
-        return isOnBackInvokedCallbackEnabled(mChecker.getContext());
-    }
-
-    /**
-     * Returns true if system gesture exclusion is needed for global gesture compatibility with
-     * windowSwipeToDismiss styleable.
-     */
-    public boolean isSystemGestureExclusionNeeded() {
-        return Checker.getBackCallbackType(mChecker.getContext())
-                == BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS;
-    }
-
     /**
      * Returns false if the legacy back behavior should be used.
      * <p>
@@ -416,7 +394,7 @@
      * {@link OnBackInvokedCallback}.
      */
     public static boolean isOnBackInvokedCallbackEnabled(@NonNull Context context) {
-        return Checker.getBackCallbackType(context) == BACK_CALLBACK_ENABLED;
+        return Checker.isOnBackInvokedCallbackEnabled(context);
     }
 
     @Override
@@ -468,29 +446,28 @@
             return mContext.get();
         }
 
-        @OnBackInvokedCallbackType
-        private static int getBackCallbackType(@Nullable Context context) {
+        private static boolean isOnBackInvokedCallbackEnabled(@Nullable Context context) {
             // new back is enabled if the feature flag is enabled AND the app does not explicitly
             // request legacy back.
             boolean featureFlagEnabled = ENABLE_PREDICTIVE_BACK;
             if (!featureFlagEnabled) {
-                return BACK_CALLBACK_DISABLED;
+                return false;
             }
 
             if (ALWAYS_ENFORCE_PREDICTIVE_BACK) {
-                Log.i(TAG, "getBackCallbackType: always enable");
-                return BACK_CALLBACK_ENABLED;
+                return true;
             }
 
             // If the context is null, return false to use legacy back.
             if (context == null) {
                 Log.w(TAG, "OnBackInvokedCallback is not enabled because context is null.");
-                return BACK_CALLBACK_DISABLED;
+                return false;
             }
 
             boolean requestsPredictiveBack = false;
 
             // Check if the context is from an activity.
+            Context originalContext = context;
             while ((context instanceof ContextWrapper) && !(context instanceof Activity)) {
                 context = ((ContextWrapper) context).getBaseContext();
             }
@@ -539,8 +516,10 @@
                     // 3. windowSwipeToDismiss=false should be respected for apps not opted in,
                     //    which disables PB & onBackPressed caused by BackAnimController's
                     //    setTrigger(true)
+                    // Use the original context to resolve the styled attribute so that they stay
+                    // true to the window.
                     TypedArray windowAttr =
-                            context.obtainStyledAttributes(
+                            originalContext.obtainStyledAttributes(
                                     new int[] {android.R.attr.windowSwipeToDismiss});
                     boolean windowSwipeToDismiss = true;
                     if (windowAttr.getIndexCount() > 0) {
@@ -552,15 +531,11 @@
                         Log.i(TAG, "falling back to windowSwipeToDismiss: " + windowSwipeToDismiss);
                     }
 
-                    if (!windowSwipeToDismiss) {
-                        return BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS;
-                    } else {
-                        return BACK_CALLBACK_ENABLED;
-                    }
+                    requestsPredictiveBack = windowSwipeToDismiss;
                 }
             }
 
-            return requestsPredictiveBack ? BACK_CALLBACK_ENABLED : BACK_CALLBACK_DISABLED;
+            return requestsPredictiveBack;
         }
     }
 }
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index 611da3c..0cbfcc5 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -155,7 +155,7 @@
     }
 
     /**
-     * Override {@link Service}'s empty implementation and listen to {@link ActivityThread} for
+     * Override {@link Service}'s empty implementation and listen to {@code ActivityThread} for
      * low memory and trim memory events.
      */
     @Override
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 1b98806..ccbf4a9 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -9,3 +9,11 @@
     is_fixed_read_only: true
     bug: "292032926"
 }
+
+flag {
+    namespace: "window_surfaces"
+    name: "explicit_refresh_rate_hints"
+    description: "Performance related hints during transitions"
+    is_fixed_read_only: true
+    bug: "300019131"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 7c931cd..9caf87f 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -13,3 +13,10 @@
   description: "On close to square display, when necessary, configuration includes status bar"
   bug: "291870756"
 }
+
+flag {
+  name: "dimmer_refactor"
+  namespace: "windowing_frontend"
+  description: "Refactor dim to fix flickers"
+  bug: "281632483,295291019"
+}
\ No newline at end of file
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/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 5dd558a..987c14c 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -28,15 +28,19 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.res.TypedArray;
 import android.os.Bundle;
 import android.view.View;
 import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.Flags;
 import android.widget.AdapterView;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -207,6 +211,11 @@
                 isEditMenuMode ? this::onTargetChecked : this::onTargetSelected);
     }
 
+    @VisibleForTesting
+    public AlertDialog getMenuDialog() {
+        return mMenuDialog;
+    }
+
     private AlertDialog createMenuDialog() {
         final String dialogTitle =
                 getString(R.string.accessibility_select_shortcut_menu_title);
@@ -216,12 +225,25 @@
                 .setAdapter(mTargetAdapter, /* listener= */ null)
                 .setOnDismissListener(dialog -> finish());
 
-        if (isUserSetupCompleted(this)) {
+        boolean allowEditing = isUserSetupCompleted(this);
+        boolean showWhenLocked = false;
+        if (Flags.allowShortcutChooserOnLockscreen()) {
+            final KeyguardManager keyguardManager = getSystemService(KeyguardManager.class);
+            if (keyguardManager != null && keyguardManager.isKeyguardLocked()) {
+                allowEditing = false;
+                showWhenLocked = true;
+            }
+        }
+        if (allowEditing) {
             final String positiveButtonText =
                     getString(R.string.edit_accessibility_shortcut_menu_button);
             builder.setPositiveButton(positiveButtonText, /* listener= */ null);
         }
 
-        return builder.create();
+        final AlertDialog dialog = builder.create();
+        if (showWhenLocked) {
+            dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+        }
+        return dialog;
     }
 }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 2909b6a..e494346 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -414,11 +414,6 @@
             "dark_launch_remote_prediction_service_enabled";
 
     /**
-     * (boolean) Whether to enable pinch resizing for PIP.
-     */
-    public static final String PIP_PINCH_RESIZE = "pip_pinch_resize";
-
-    /**
      * (boolean) Whether to enable stashing for PIP.
      */
     public static final String PIP_STASHING = "pip_stashing";
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index cb2d934..b1d22e0 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -86,6 +86,28 @@
         public static final Flag ENABLE_ATTENTION_HELPER_REFACTOR = devFlag(
                 "persist.debug.sysui.notification.enable_attention_helper_refactor");
 
+        // TODO b/291899544: for released flags, use resource config values
+        /** Value used by polite notif. feature */
+        public static final Flag NOTIF_COOLDOWN_T1 = devFlag(
+                "persist.debug.sysui.notification.notif_cooldown_t1", 5000);
+        /** Value used by polite notif. feature */
+        public static final Flag NOTIF_COOLDOWN_T2 = devFlag(
+                "persist.debug.sysui.notification.notif_cooldown_t2", 3000);
+        /** Value used by polite notif. feature */
+        public static final Flag NOTIF_VOLUME1 = devFlag(
+                "persist.debug.sysui.notification.notif_volume1", 30);
+        public static final Flag NOTIF_VOLUME2 = devFlag(
+                "persist.debug.sysui.notification.notif_volume2", 0);
+        /** Value used by polite notif. feature. -1 to ignore the counter */
+        public static final Flag NOTIF_COOLDOWN_COUNTER_RESET = devFlag(
+                "persist.debug.sysui.notification.notif_cooldown_counter_reset", 10);
+        /**
+         * Value used by polite notif. feature: cooldown behavior/strategy. Valid values: rule1,
+         * rule2
+         */
+        public static final Flag NOTIF_COOLDOWN_RULE = devFlag(
+                "persist.debug.sysui.notification.notif_cooldown_rule", "rule1");
+
         /** b/301242692: Visit extra URIs used in notifications to prevent security issues. */
         public static final Flag VISIT_RISKY_URIS = devFlag(
                 "persist.sysui.notification.visit_risky_uris");
@@ -97,6 +119,10 @@
     public interface FlagResolver {
         /** Is the flag enabled? */
         boolean isEnabled(Flag flag);
+        /** Get the flag value (integer) */
+        int getIntValue(Flag flag);
+        /** Get the flag value (string) */
+        String getStringValue(Flag flag);
     }
 
     /** The primary, immutable resolver returned by getResolver() */
@@ -134,6 +160,22 @@
     }
 
     /**
+     * Creates a flag that with a default integer value in debuggable builds.
+     */
+    @VisibleForTesting
+    public static Flag devFlag(String name, int defaultValue) {
+        return new Flag(name, defaultValue, null);
+    }
+
+    /**
+     * Creates a flag that with a default string value in debuggable builds.
+     */
+    @VisibleForTesting
+    public static Flag devFlag(String name, String defaultValue) {
+        return new Flag(name, defaultValue, null);
+    }
+
+    /**
      * Creates a flag that is disabled by default in debuggable builds.
      * It can be enabled or force-disabled by setting this flag's SystemProperty to 1 or 0.
      * If this flag's SystemProperty is not set, the flag can be enabled by setting the
@@ -161,6 +203,8 @@
     public static final class Flag {
         public final String mSysPropKey;
         public final boolean mDefaultValue;
+        public final int mDefaultIntValue;
+        public final String mDefaultStringValue;
         @Nullable
         public final Flag mDebugDefault;
 
@@ -170,6 +214,24 @@
             mSysPropKey = sysPropKey;
             mDefaultValue = defaultValue;
             mDebugDefault = debugDefault;
+            mDefaultIntValue = 0;
+            mDefaultStringValue = null;
+        }
+
+        public Flag(@NonNull String sysPropKey, int defaultValue, @Nullable Flag debugDefault) {
+            mSysPropKey = sysPropKey;
+            mDefaultIntValue = defaultValue;
+            mDebugDefault = debugDefault;
+            mDefaultValue = false;
+            mDefaultStringValue = null;
+        }
+
+        public Flag(@NonNull String sysPropKey, String defaultValue, @Nullable Flag debugDefault) {
+            mSysPropKey = sysPropKey;
+            mDefaultStringValue = defaultValue;
+            mDebugDefault = debugDefault;
+            mDefaultValue = false;
+            mDefaultIntValue = 0;
         }
     }
 
@@ -181,6 +243,16 @@
         public boolean isEnabled(Flag flag) {
             return flag.mDefaultValue;
         }
+
+        @Override
+        public int getIntValue(Flag flag) {
+            return flag.mDefaultIntValue;
+        }
+
+        @Override
+        public String getStringValue(Flag flag) {
+            return flag.mDefaultStringValue;
+        }
     }
 
     /** Implementation of the interface used in debuggable builds. */
@@ -199,5 +271,23 @@
         public boolean getBoolean(String key, boolean defaultValue) {
             return SystemProperties.getBoolean(key, defaultValue);
         }
+
+        /** Look up the value; overridable for tests to avoid needing to set SystemProperties */
+        @VisibleForTesting
+        public int getIntValue(Flag flag) {
+            if (flag.mDebugDefault == null) {
+                return SystemProperties.getInt(flag.mSysPropKey, flag.mDefaultIntValue);
+            }
+            return SystemProperties.getInt(flag.mSysPropKey, getIntValue(flag.mDebugDefault));
+        }
+
+        /** Look up the value; overridable for tests to avoid needing to set SystemProperties */
+        @VisibleForTesting
+        public String getStringValue(Flag flag) {
+            if (flag.mDebugDefault == null) {
+                return SystemProperties.get(flag.mSysPropKey, flag.mDefaultStringValue);
+            }
+            return SystemProperties.get(flag.mSysPropKey, getStringValue(flag.mDebugDefault));
+        }
     }
 }
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/java/com/android/internal/display/RefreshRateSettingsUtils.java b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java
new file mode 100644
index 0000000..e55c641
--- /dev/null
+++ b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java
@@ -0,0 +1,56 @@
+/*
+ * 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.internal.display;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.util.Log;
+import android.view.Display;
+
+/**
+ * Constants and utility methods for refresh rate settings.
+ */
+public class RefreshRateSettingsUtils {
+
+    private static final String TAG = "RefreshRateSettingsUtils";
+
+    public static final float DEFAULT_REFRESH_RATE = 60f;
+
+    /**
+     * Find the highest refresh rate among all the modes of the default display.
+     *
+     * @param context The context
+     * @return The highest refresh rate
+     */
+    public static float findHighestRefreshRateForDefaultDisplay(Context context) {
+        final DisplayManager dm = context.getSystemService(DisplayManager.class);
+        final Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
+
+        if (display == null) {
+            Log.w(TAG, "No valid default display device");
+            return DEFAULT_REFRESH_RATE;
+        }
+
+        float maxRefreshRate = DEFAULT_REFRESH_RATE;
+        for (Display.Mode mode : display.getSupportedModes()) {
+            if (mode.getRefreshRate() > maxRefreshRate) {
+                maxRefreshRate = mode.getRefreshRate();
+            }
+        }
+        return maxRefreshRate;
+    }
+}
diff --git a/core/java/com/android/internal/foldables/Android.bp b/core/java/com/android/internal/foldables/Android.bp
new file mode 100644
index 0000000..f1d06da
--- /dev/null
+++ b/core/java/com/android/internal/foldables/Android.bp
@@ -0,0 +1,7 @@
+aconfig_declarations {
+    name: "fold_lock_setting_flags",
+    package: "com.android.internal.foldables.flags",
+    srcs: [
+        "fold_lock_setting_flags.aconfig",
+    ],
+}
diff --git a/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java b/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java
index 4e3888a..a115ecf 100644
--- a/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java
+++ b/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java
@@ -17,16 +17,23 @@
 package com.android.internal.foldables;
 
 import android.content.res.Resources;
+import android.os.Build;
 import android.sysprop.FoldLockBehaviorProperties;
+import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.internal.foldables.flags.Flags;
+
+import java.util.function.Supplier;
 
 /**
  * Wrapper class to access {@link FoldLockBehaviorProperties} and also assists with testing
  */
 public class FoldLockSettingAvailabilityProvider {
 
-    boolean mFoldLockBehaviorResourceValue;
+    private static final String TAG = "FoldLockSettingAvailabilityProvider";
+    private final boolean mFoldLockBehaviorResourceValue;
+    private final Supplier<Boolean> mFoldLockSettingEnabled = Flags::foldLockSettingEnabled;
 
     public FoldLockSettingAvailabilityProvider(Resources resources) {
         mFoldLockBehaviorResourceValue = resources.getBoolean(
@@ -35,6 +42,22 @@
 
     public boolean isFoldLockBehaviorAvailable() {
         return mFoldLockBehaviorResourceValue
-                && FoldLockBehaviorProperties.fold_lock_setting_enabled().orElse(false);
+                && flagOrSystemProperty();
+    }
+
+    private boolean flagOrSystemProperty() {
+        if ((Build.IS_ENG || Build.IS_USERDEBUG)
+                && FoldLockBehaviorProperties.fold_lock_setting_enabled().orElse(false)) {
+            return true;
+        }
+        try {
+            return mFoldLockSettingEnabled.get();
+        } catch (Throwable ex) {
+            Slog.i(TAG,
+                    "Flags not ready yet. Return false for "
+                            + Flags.FLAG_FOLD_LOCK_SETTING_ENABLED,
+                    ex);
+            return false;
+        }
     }
 }
diff --git a/core/java/com/android/internal/foldables/OWNERS b/core/java/com/android/internal/foldables/OWNERS
new file mode 100644
index 0000000..6ce1ee4
--- /dev/null
+++ b/core/java/com/android/internal/foldables/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/display/OWNERS
diff --git a/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig b/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig
new file mode 100644
index 0000000..44f436ea
--- /dev/null
+++ b/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.internal.foldables.flags"
+
+flag {
+    name: "fold_lock_setting_enabled"
+    namespace: "display_manager"
+    description: "Feature flag for Fold Lock Setting"
+    bug: "274447767"
+    is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index d3103f1..0399430 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -37,7 +37,6 @@
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.TimeUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -78,7 +77,7 @@
     private static final String TAG = "BatteryStatsHistory";
 
     // Current on-disk Parcel version. Must be updated when the format of the parcelable changes
-    private static final int VERSION = 209;
+    private static final int VERSION = 210;
 
     private static final String HISTORY_DIR = "battery-history";
     private static final String FILE_SUFFIX = ".bh";
@@ -194,10 +193,11 @@
     private int mNextHistoryTagIdx = 0;
     private int mNumHistoryTagChars = 0;
     private int mHistoryBufferLastPos = -1;
-    private long mLastHistoryElapsedRealtimeMs = 0;
     private long mTrackRunningHistoryElapsedRealtimeMs = 0;
     private long mTrackRunningHistoryUptimeMs = 0;
-    private long mHistoryBaseTimeMs;
+    private final MonotonicClock mMonotonicClock;
+    // Monotonic time when we started writing to the history buffer
+    private long mHistoryBufferStartTime;
     private final ArraySet<PowerStats.Descriptor> mWrittenPowerStatsDescriptors = new ArraySet<>();
     private byte mLastHistoryStepLevel = 0;
     private boolean mMutable = true;
@@ -307,23 +307,26 @@
      * @param maxHistoryBufferSize the most amount of RAM to used for buffering of history steps
      */
     public BatteryStatsHistory(File systemDir, int maxHistoryFiles, int maxHistoryBufferSize,
-            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+            MonotonicClock monotonicClock) {
         this(Parcel.obtain(), systemDir, maxHistoryFiles, maxHistoryBufferSize,
-                stepDetailsCalculator, clock, new TraceDelegate());
+                stepDetailsCalculator, clock, monotonicClock, new TraceDelegate());
         initHistoryBuffer();
     }
 
     @VisibleForTesting
     public BatteryStatsHistory(Parcel historyBuffer, File systemDir,
             int maxHistoryFiles, int maxHistoryBufferSize,
-            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, TraceDelegate tracer) {
+            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+            MonotonicClock monotonicClock, TraceDelegate tracer) {
         this(historyBuffer, systemDir, maxHistoryFiles, maxHistoryBufferSize, stepDetailsCalculator,
-                clock, tracer, null);
+                clock, monotonicClock, tracer, null);
     }
 
     private BatteryStatsHistory(Parcel historyBuffer, File systemDir,
             int maxHistoryFiles, int maxHistoryBufferSize,
-            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, TraceDelegate tracer,
+            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+            MonotonicClock monotonicClock, TraceDelegate tracer,
             BatteryStatsHistory writableHistory) {
         mHistoryBuffer = historyBuffer;
         mSystemDir = systemDir;
@@ -332,6 +335,7 @@
         mStepDetailsCalculator = stepDetailsCalculator;
         mTracer = tracer;
         mClock = clock;
+        mMonotonicClock = monotonicClock;
         mWritableHistory = writableHistory;
         if (mWritableHistory != null) {
             mMutable = false;
@@ -381,16 +385,18 @@
     }
 
     private BatteryHistoryFile makeBatteryHistoryFile() {
-        return new BatteryHistoryFile(mHistoryDir, mClock.elapsedRealtime() + mHistoryBaseTimeMs);
+        return new BatteryHistoryFile(mHistoryDir, mMonotonicClock.monotonicTime());
     }
 
     public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize,
-            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+            MonotonicClock monotonicClock) {
         mMaxHistoryFiles = maxHistoryFiles;
         mMaxHistoryBufferSize = maxHistoryBufferSize;
         mStepDetailsCalculator = stepDetailsCalculator;
         mTracer = new TraceDelegate();
         mClock = clock;
+        mMonotonicClock = monotonicClock;
 
         mHistoryBuffer = Parcel.obtain();
         mSystemDir = null;
@@ -417,16 +423,16 @@
         mHistoryBuffer = Parcel.obtain();
         mHistoryBuffer.unmarshall(historyBlob, 0, historyBlob.length);
 
+        mMonotonicClock = null;
         readFromParcel(parcel, true /* useBlobs */);
     }
 
     private void initHistoryBuffer() {
-        mHistoryBaseTimeMs = 0;
-        mLastHistoryElapsedRealtimeMs = 0;
         mTrackRunningHistoryElapsedRealtimeMs = 0;
         mTrackRunningHistoryUptimeMs = 0;
         mWrittenPowerStatsDescriptors.clear();
 
+        mHistoryBufferStartTime = mMonotonicClock.monotonicTime();
         mHistoryBuffer.setDataSize(0);
         mHistoryBuffer.setDataPosition(0);
         mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
@@ -466,7 +472,7 @@
             historyBufferCopy.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
 
             return new BatteryStatsHistory(historyBufferCopy, mSystemDir, 0, 0, null, null, null,
-                    this);
+                    null, this);
         }
     }
 
@@ -491,7 +497,7 @@
      * When {@link #mHistoryBuffer} reaches {@link BatteryStatsImpl.Constants#MAX_HISTORY_BUFFER},
      * create next history file.
      */
-    public void startNextFile() {
+    public void startNextFile(long elapsedRealtimeMs) {
         if (mMaxHistoryFiles == 0) {
             Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history");
             return;
@@ -517,6 +523,7 @@
             Slog.e(TAG, "Could not create history file: " + mActiveFile.getBaseFile());
         }
 
+        mHistoryBufferStartTime = mMonotonicClock.monotonicTime(elapsedRealtimeMs);
         mHistoryBuffer.setDataSize(0);
         mHistoryBuffer.setDataPosition(0);
         mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
@@ -699,9 +706,12 @@
         if (mHistoryParcels != null) {
             while (mParcelIndex < mHistoryParcels.size()) {
                 final Parcel p = mHistoryParcels.get(mParcelIndex++);
-                if (!skipHead(p)) {
+                if (!verifyVersion(p)) {
                     continue;
                 }
+                // skip monotonic time field.
+                p.readLong();
+
                 final int bufSize = p.readInt();
                 final int curPos = p.dataPosition();
                 mCurrentParcelEnd = curPos + bufSize;
@@ -745,24 +755,36 @@
         }
         out.unmarshall(raw, 0, raw.length);
         out.setDataPosition(0);
-        return skipHead(out);
+        if (!verifyVersion(out)) {
+            return false;
+        }
+        // skip monotonic time field.
+        out.readLong();
+        return true;
     }
 
     /**
-     * Skip the header part of history parcel.
+     * Verify header part of history parcel.
      *
-     * @param p history parcel to skip head.
      * @return true if version match, false if not.
      */
-    private boolean skipHead(Parcel p) {
+    private boolean verifyVersion(Parcel p) {
         p.setDataPosition(0);
         final int version = p.readInt();
-        if (version != VERSION) {
-            return false;
-        }
-        // skip historyBaseTime field.
-        p.readLong();
-        return true;
+        return version == VERSION;
+    }
+
+    /**
+     * Extracts the monotonic time, as per {@link MonotonicClock}, from the supplied battery history
+     * buffer.
+     */
+    public long getHistoryBufferStartTime(Parcel p) {
+        int pos = p.dataPosition();
+        p.setDataPosition(0);
+        p.readInt();        // Skip the version field
+        long monotonicTime = p.readLong();
+        p.setDataPosition(pos);
+        return monotonicTime;
     }
 
     /**
@@ -1438,7 +1460,8 @@
             throw new ConcurrentModificationException("Battery history is not writable");
         }
 
-        final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
+        final long timeDiffMs = mMonotonicClock.monotonicTime(elapsedRealtimeMs)
+                                - mHistoryLastWritten.time;
         final int diffStates = mHistoryLastWritten.states ^ cur.states;
         final int diffStates2 = mHistoryLastWritten.states2 ^ cur.states2;
         final int lastDiffStates = mHistoryLastWritten.states ^ mHistoryLastLastWritten.states;
@@ -1476,7 +1499,9 @@
             mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
             mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
             mHistoryBufferLastPos = -1;
-            elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTimeMs;
+
+            elapsedRealtimeMs -= timeDiffMs;
+
             // If the last written history had a wakelock tag, we need to retain it.
             // Note that the condition above made sure that we aren't in a case where
             // both it and the current history item have a wakelock tag.
@@ -1513,7 +1538,7 @@
             HistoryItem copy = new HistoryItem();
             copy.setTo(cur);
 
-            startNextFile();
+            startNextFile(elapsedRealtimeMs);
 
             // startRecordingHistory will reset mHistoryCur.
             startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
@@ -1548,7 +1573,7 @@
         mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
         mHistoryLastLastWritten.setTo(mHistoryLastWritten);
         final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence;
-        mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur);
+        mHistoryLastWritten.setTo(mMonotonicClock.monotonicTime(elapsedRealtimeMs), cmd, cur);
         if (mHistoryLastWritten.time < mHistoryLastLastWritten.time - 60000) {
             Slog.wtf(TAG, "Significantly earlier event written to battery history:"
                     + " time=" + mHistoryLastWritten.time
@@ -1556,7 +1581,6 @@
         }
         mHistoryLastWritten.tagsFirstOccurrence = hasTags;
         writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
-        mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
         cur.wakelockTag = null;
         cur.wakeReasonTag = null;
         cur.eventCode = HistoryItem.EVENT_NONE;
@@ -1926,6 +1950,10 @@
             return;
         }
 
+        // Save the monotonic time first, so that even if the history write below fails,
+        // we still wouldn't end up with overlapping history timelines.
+        mMonotonicClock.write();
+
         Parcel p = Parcel.obtain();
         try {
             final long start = SystemClock.uptimeMillis();
@@ -1951,8 +1979,7 @@
             return;
         }
 
-        final long historyBaseTime = in.readLong();
-
+        mHistoryBufferStartTime = in.readLong();
         mHistoryBuffer.setDataSize(0);
         mHistoryBuffer.setDataPosition(0);
 
@@ -1972,39 +1999,11 @@
             mHistoryBuffer.appendFrom(in, curPos, bufSize);
             in.setDataPosition(curPos + bufSize);
         }
-
-        mHistoryBaseTimeMs = historyBaseTime;
-        if (DEBUG) {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("****************** NEW mHistoryBaseTimeMs: ");
-            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
-            Slog.i(TAG, sb.toString());
-        }
-
-        if (mHistoryBaseTimeMs > 0) {
-            long elapsedRealtimeMs = mClock.elapsedRealtime();
-            mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
-            mHistoryBaseTimeMs = mHistoryBaseTimeMs - elapsedRealtimeMs + 1;
-            if (DEBUG) {
-                StringBuilder sb = new StringBuilder(128);
-                sb.append("****************** ADJUSTED mHistoryBaseTimeMs: ");
-                TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
-                Slog.i(TAG, sb.toString());
-            }
-        }
     }
 
     private void writeHistoryBuffer(Parcel out) {
-        if (DEBUG) {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("****************** WRITING mHistoryBaseTimeMs: ");
-            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
-            sb.append(" mLastHistoryElapsedRealtimeMs: ");
-            TimeUtils.formatDuration(mLastHistoryElapsedRealtimeMs, sb);
-            Slog.i(TAG, sb.toString());
-        }
         out.writeInt(BatteryStatsHistory.VERSION);
-        out.writeLong(mHistoryBaseTimeMs + mLastHistoryElapsedRealtimeMs);
+        out.writeLong(mHistoryBufferStartTime);
         out.writeInt(mHistoryBuffer.dataSize());
         if (DEBUG) {
             Slog.i(TAG, "***************** WRITING HISTORY: "
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index a5d2d0f..6bd5898 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.os;
 
-import android.annotation.CurrentTimeMillisLong;
 import android.annotation.NonNull;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
@@ -34,8 +33,8 @@
     private static final boolean DEBUG = false;
     private static final String TAG = "BatteryStatsHistoryItr";
     private final BatteryStatsHistory mBatteryStatsHistory;
-    private final @CurrentTimeMillisLong long mStartTimeMs;
-    private final @CurrentTimeMillisLong long mEndTimeMs;
+    private final long mStartTimeMs;
+    private final long mEndTimeMs;
     private final BatteryStats.HistoryStepDetails mReadHistoryStepDetails =
             new BatteryStats.HistoryStepDetails();
     private final SparseArray<BatteryStats.HistoryTag> mHistoryTags = new SparseArray<>();
@@ -43,10 +42,10 @@
             new PowerStats.DescriptorRegistry();
     private BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
     private boolean mNextItemReady;
+    private boolean mTimeInitialized;
 
-    public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history,
-            @CurrentTimeMillisLong long startTimeMs,
-            @CurrentTimeMillisLong long endTimeMs) {
+    public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history, long startTimeMs,
+            long endTimeMs) {
         mBatteryStatsHistory = history;
         mStartTimeMs = startTimeMs;
         mEndTimeMs = (endTimeMs != 0) ? endTimeMs : Long.MAX_VALUE;
@@ -82,7 +81,12 @@
                 break;
             }
 
-            final long lastRealtimeMs = mHistoryItem.time;
+            if (!mTimeInitialized) {
+                mHistoryItem.time = mBatteryStatsHistory.getHistoryBufferStartTime(p);
+                mTimeInitialized = true;
+            }
+
+            final long lastMonotonicTimeMs = mHistoryItem.time;
             final long lastWalltimeMs = mHistoryItem.currentTime;
             try {
                 readHistoryDelta(p, mHistoryItem);
@@ -93,12 +97,13 @@
             if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
                     && mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET
                     && lastWalltimeMs != 0) {
-                mHistoryItem.currentTime = lastWalltimeMs + (mHistoryItem.time - lastRealtimeMs);
+                mHistoryItem.currentTime =
+                        lastWalltimeMs + (mHistoryItem.time - lastMonotonicTimeMs);
             }
-            if (mEndTimeMs != 0 && mHistoryItem.currentTime >= mEndTimeMs) {
+            if (mEndTimeMs != 0 && mHistoryItem.time >= mEndTimeMs) {
                 break;
             }
-            if (mHistoryItem.currentTime >= mStartTimeMs) {
+            if (mHistoryItem.time >= mStartTimeMs) {
                 mNextItemReady = true;
                 return;
             }
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index 5ea6ba8..1f44b33 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -191,6 +191,27 @@
     }
 
     /**
+     * Sets the new values for the given state.
+     */
+    public void setValues(int state, long[] values) {
+        if (state < 0 || state >= mStateCount) {
+            throw new IllegalArgumentException(
+                    "State: " + state + ", outside the range: [0-" + (mStateCount - 1) + "]");
+        }
+        if (values.length != mLength) {
+            throw new IllegalArgumentException(
+                    "Invalid array length: " + values.length + ", expected: " + mLength);
+        }
+        LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
+        if (container == null || container.mLength != values.length) {
+            container = new LongArrayContainer(values.length);
+        }
+        container.setValues(values);
+        native_setValues(mNativeObject, state, container.mNativeObject);
+        sTmpArrayContainer.set(container);
+    }
+
+    /**
      * Sets the new values.  The delta between the previously set values and these values
      * is distributed among the state according to the time the object spent in those states
      * since the previous call to updateValues.
@@ -317,6 +338,10 @@
     private static native void native_setState(long nativeObject, int state, long timestampMs);
 
     @CriticalNative
+    private static native void native_setValues(long nativeObject, int state,
+            long longArrayContainerNativeObject);
+
+    @CriticalNative
     private static native void native_updateValues(long nativeObject,
             long longArrayContainerNativeObject, long timestampMs);
 
diff --git a/core/java/com/android/internal/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java
new file mode 100644
index 0000000..6f114e3
--- /dev/null
+++ b/core/java/com/android/internal/os/MonotonicClock.java
@@ -0,0 +1,145 @@
+/*
+ * 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.internal.os;
+
+import android.annotation.NonNull;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * A clock that is similar to SystemClock#elapsedRealtime(), except that it is not reset
+ * on reboot, but keeps going.
+ */
+public class MonotonicClock {
+    private static final String TAG = "MonotonicClock";
+
+    private static final String XML_TAG_MONOTONIC_TIME = "monotonic_time";
+    private static final String XML_ATTR_TIMESHIFT = "timeshift";
+
+    private final AtomicFile mFile;
+    private final Clock mClock;
+    private long mTimeshift;
+
+    public MonotonicClock(File file) {
+        mFile = new AtomicFile(file);
+        mClock = Clock.SYSTEM_CLOCK;
+        read();
+    }
+
+    public MonotonicClock(long monotonicTime, @NonNull Clock clock) {
+        mClock = clock;
+        mFile = null;
+        mTimeshift = monotonicTime - mClock.elapsedRealtime();
+    }
+
+    /**
+     * Returns time in milliseconds, based on SystemClock.elapsedTime, adjusted so that
+     * after a device reboot the time keeps increasing.
+     */
+    public long monotonicTime() {
+        return monotonicTime(mClock.elapsedRealtime());
+    }
+
+    /**
+     * Like {@link #monotonicTime()}, except the elapsed time is supplied as an argument instead
+     * of being read from the Clock.
+     */
+    public long monotonicTime(long elapsedRealtimeMs) {
+        return mTimeshift + elapsedRealtimeMs;
+    }
+
+    private void read() {
+        if (!mFile.exists()) {
+            return;
+        }
+
+        try {
+            readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser());
+        } catch (IOException e) {
+            Log.e(TAG, "Cannot load monotonic clock from " + mFile.getBaseFile(), e);
+        }
+    }
+
+    /**
+     * Saves the timeshift into a file.  Call this method just before system shutdown, after
+     * writing the last battery history event.
+     */
+    public void write() {
+        if (mFile == null) {
+            return;
+        }
+
+        mFile.write(out -> {
+            try {
+                writeXml(out, Xml.newBinarySerializer());
+                out.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e);
+            }
+        });
+    }
+
+    /**
+     * Parses an XML file containing the persistent state of the monotonic clock.
+     */
+    @VisibleForTesting
+    public void readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException {
+        long savedTimeshift = 0;
+        try {
+            parser.setInput(inputStream, StandardCharsets.UTF_8.name());
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.END_DOCUMENT) {
+                if (eventType == XmlPullParser.START_TAG
+                        && parser.getName().equals(XML_TAG_MONOTONIC_TIME)) {
+                    savedTimeshift = parser.getAttributeLong(null, XML_ATTR_TIMESHIFT);
+                }
+                eventType = parser.next();
+            }
+        } catch (XmlPullParserException e) {
+            throw new IOException(e);
+        }
+        mTimeshift = savedTimeshift - mClock.elapsedRealtime();
+    }
+
+    /**
+     * Creates an XML file containing the persistent state of the monotonic clock.
+     */
+    @VisibleForTesting
+    public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
+        serializer.setOutput(out, StandardCharsets.UTF_8.name());
+        serializer.startDocument(null, true);
+        serializer.startTag(null, XML_TAG_MONOTONIC_TIME);
+        serializer.attributeLong(null, XML_ATTR_TIMESHIFT, monotonicTime());
+        serializer.endTag(null, XML_TAG_MONOTONIC_TIME);
+        serializer.endDocument();
+    }
+}
diff --git a/core/java/com/android/internal/os/MultiStateStats.java b/core/java/com/android/internal/os/MultiStateStats.java
index f971849..dc5055a 100644
--- a/core/java/com/android/internal/os/MultiStateStats.java
+++ b/core/java/com/android/internal/os/MultiStateStats.java
@@ -16,9 +16,17 @@
 
 package com.android.internal.os;
 
+import android.util.Slog;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.Arrays;
 
@@ -29,15 +37,21 @@
  * values;
  */
 public class MultiStateStats {
+    private static final String TAG = "MultiStateStats";
+
+    private static final String XML_TAG_STATS = "stats";
+
     /**
      * A set of states, e.g. on-battery, screen-on, procstate.  The state values are integers
      * from 0 to States.mLabels.length
      */
     public static class States {
+        final String mName;
         final boolean mTracked;
         final String[] mLabels;
 
-        public States(boolean tracked, String... labels) {
+        public States(String name, boolean tracked, String... labels) {
+            mName = name;
             this.mTracked = tracked;
             this.mLabels = labels;
         }
@@ -155,11 +169,28 @@
                    >>> mStateBitFieldShifts[stateIndex];
         }
 
-        private int setStateInComposite(int baseCompositeState, int stateIndex, int value) {
+        int setStateInComposite(int baseCompositeState, int stateIndex, int value) {
             return (baseCompositeState & ~mStateBitFieldMasks[stateIndex])
                     | (value << mStateBitFieldShifts[stateIndex]);
         }
 
+        int setStateInComposite(int compositeState, String stateName, String stateLabel) {
+            for (int stateIndex = 0; stateIndex < mStates.length; stateIndex++) {
+                States stateConfig = mStates[stateIndex];
+                if (stateConfig.mName.equals(stateName)) {
+                    for (int state = 0; state < stateConfig.mLabels.length; state++) {
+                        if (stateConfig.mLabels[state].equals(stateLabel)) {
+                            return setStateInComposite(compositeState, stateIndex, state);
+                        }
+                    }
+                    Slog.e(TAG, "Unexpected label '" + stateLabel + "' for state: " + stateName);
+                    return -1;
+                }
+            }
+            Slog.e(TAG, "Unsupported state: " + stateName);
+            return -1;
+        }
+
         /**
          * Allocates a new stats container using this Factory's configuration.
          */
@@ -195,6 +226,10 @@
             }
             return serialState;
         }
+
+        int getSerialState(int compositeState) {
+            return mCompositeToSerialState[compositeState];
+        }
     }
 
     private final Factory mFactory;
@@ -254,6 +289,106 @@
     }
 
     /**
+     * Stores contents in an XML doc.
+     */
+    public void writeXml(TypedXmlSerializer serializer) throws IOException {
+        long[] tmpArray = new long[mCounter.getArrayLength()];
+        writeXmlAllStates(serializer, new int[mFactory.mStates.length], 0, tmpArray);
+    }
+
+    private void writeXmlAllStates(TypedXmlSerializer serializer, int[] states, int stateIndex,
+            long[] values) throws IOException {
+        if (stateIndex < states.length) {
+            if (!mFactory.mStates[stateIndex].mTracked) {
+                writeXmlAllStates(serializer, states, stateIndex + 1, values);
+                return;
+            }
+
+            for (int i = 0; i < mFactory.mStates[stateIndex].mLabels.length; i++) {
+                states[stateIndex] = i;
+                writeXmlAllStates(serializer, states, stateIndex + 1, values);
+            }
+            return;
+        }
+
+        mCounter.getCounts(values, mFactory.getSerialState(states));
+        boolean nonZero = false;
+        for (long value : values) {
+            if (value != 0) {
+                nonZero = true;
+                break;
+            }
+        }
+        if (!nonZero) {
+            return;
+        }
+
+        serializer.startTag(null, XML_TAG_STATS);
+
+        for (int i = 0; i < states.length; i++) {
+            if (mFactory.mStates[i].mTracked && states[i] != 0) {
+                serializer.attribute(null, mFactory.mStates[i].mName,
+                        mFactory.mStates[i].mLabels[states[i]]);
+            }
+        }
+        for (int i = 0; i < values.length; i++) {
+            if (values[i] != 0) {
+                serializer.attributeLong(null, "_" + i, values[i]);
+            }
+        }
+        serializer.endTag(null, XML_TAG_STATS);
+    }
+
+    /**
+     * Populates the object with contents in an XML doc. The parser is expected to be
+     * positioned on the opening tag of the corresponding element.
+     */
+    public boolean readFromXml(TypedXmlPullParser parser) throws XmlPullParserException,
+            IOException {
+        String outerTag = parser.getName();
+        long[] tmpArray = new long[mCounter.getArrayLength()];
+        int eventType = parser.getEventType();
+        while (eventType != XmlPullParser.END_DOCUMENT
+               && !(eventType == XmlPullParser.END_TAG && parser.getName().equals(outerTag))) {
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals(XML_TAG_STATS)) {
+                    Arrays.fill(tmpArray, 0);
+                    int compositeState = 0;
+                    int attributeCount = parser.getAttributeCount();
+                    for (int i = 0; i < attributeCount; i++) {
+                        String attributeName = parser.getAttributeName(i);
+                        if (attributeName.startsWith("_")) {
+                            int index;
+                            try {
+                                index = Integer.parseInt(attributeName.substring(1));
+                            } catch (NumberFormatException e) {
+                                throw new XmlPullParserException(
+                                        "Unexpected index syntax: " + attributeName, parser, e);
+                            }
+                            if (index < 0 || index >= tmpArray.length) {
+                                Slog.e(TAG, "State index out of bounds: " + index
+                                            + " length: " + tmpArray.length);
+                                return false;
+                            }
+                            tmpArray[index] = parser.getAttributeLong(i);
+                        } else {
+                            String attributeValue = parser.getAttributeValue(i);
+                            compositeState = mFactory.setStateInComposite(compositeState,
+                                    attributeName, attributeValue);
+                            if (compositeState == -1) {
+                                return false;
+                            }
+                        }
+                    }
+                    mCounter.setValues(mFactory.getSerialState(compositeState), tmpArray);
+                }
+            }
+            eventType = parser.next();
+        }
+        return true;
+    }
+
+    /**
      * Prints the accumulated stats, one line of every combination of states that has data.
      */
     public void dump(PrintWriter pw) {
diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS
index e35b7f1..996e424 100644
--- a/core/java/com/android/internal/os/OWNERS
+++ b/core/java/com/android/internal/os/OWNERS
@@ -5,14 +5,10 @@
 per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS
 
 # BatteryStats
-per-file BatterySipper.java = file:/BATTERY_STATS_OWNERS
 per-file BatteryStats* = file:/BATTERY_STATS_OWNERS
-per-file BatteryUsageStats* = file:/BATTERY_STATS_OWNERS
-per-file *ChargeCalculator* = file:/BATTERY_STATS_OWNERS
-per-file *PowerCalculator* = file:/BATTERY_STATS_OWNERS
-per-file *PowerEstimator* = file:/BATTERY_STATS_OWNERS
-per-file *PowerStats* = file:/BATTERY_STATS_OWNERS
 per-file *Kernel* = file:/BATTERY_STATS_OWNERS
+per-file *Clock* = file:/BATTERY_STATS_OWNERS
 per-file *MultiState* = file:/BATTERY_STATS_OWNERS
 per-file *PowerProfile* = file:/BATTERY_STATS_OWNERS
+per-file *PowerStats* = file:/BATTERY_STATS_OWNERS
 
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 8f66d1f..1130a45 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -24,9 +24,16 @@
 import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.util.IndentingPrintWriter;
-import android.util.Log;
+import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
 import java.util.Arrays;
 import java.util.Objects;
 
@@ -61,6 +68,13 @@
      * to adjust the algorithm in accordance with the stats available on the device.
      */
     public static class Descriptor {
+        public static final String XML_TAG_DESCRIPTOR = "descriptor";
+        private static final String XML_ATTR_ID = "id";
+        private static final String XML_ATTR_NAME = "name";
+        private static final String XML_ATTR_STATS_ARRAY_LENGTH = "stats-array-length";
+        private static final String XML_ATTR_UID_STATS_ARRAY_LENGTH = "uid-stats-array-length";
+        private static final String XML_TAG_EXTRAS = "extras";
+
         /**
          * {@link BatteryConsumer.PowerComponent} (e.g. CPU, WIFI etc) that this snapshot relates
          * to; or a custom power component ID (if the value
@@ -126,7 +140,7 @@
             int firstWord = parcel.readInt();
             int version = (firstWord & PARCEL_FORMAT_VERSION_MASK) >>> PARCEL_FORMAT_VERSION_SHIFT;
             if (version != PARCEL_FORMAT_VERSION) {
-                Log.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has "
+                Slog.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has "
                            + "changed from " + version + " to " + PARCEL_FORMAT_VERSION);
                 return null;
             }
@@ -155,6 +169,71 @@
                     that.extras);  // Since the Parcel is now unparceled, do a deep comparison
         }
 
+        /**
+         * Stores contents in an XML doc.
+         */
+        public void writeXml(TypedXmlSerializer serializer) throws IOException {
+            serializer.startTag(null, XML_TAG_DESCRIPTOR);
+            serializer.attributeInt(null, XML_ATTR_ID, powerComponentId);
+            serializer.attribute(null, XML_ATTR_NAME, name);
+            serializer.attributeInt(null, XML_ATTR_STATS_ARRAY_LENGTH, statsArrayLength);
+            serializer.attributeInt(null, XML_ATTR_UID_STATS_ARRAY_LENGTH, uidStatsArrayLength);
+            try {
+                serializer.startTag(null, XML_TAG_EXTRAS);
+                extras.saveToXml(serializer);
+                serializer.endTag(null, XML_TAG_EXTRAS);
+            } catch (XmlPullParserException e) {
+                throw new IOException(e);
+            }
+            serializer.endTag(null, XML_TAG_DESCRIPTOR);
+        }
+
+        /**
+         * Creates a Descriptor by parsing an XML doc.  The parser is expected to be positioned
+         * on or before the opening "descriptor" tag.
+         */
+        public static Descriptor createFromXml(TypedXmlPullParser parser)
+                throws XmlPullParserException, IOException {
+            int powerComponentId = -1;
+            String name = null;
+            int statsArrayLength = 0;
+            int uidStatsArrayLength = 0;
+            PersistableBundle extras = null;
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.END_DOCUMENT
+                   && !(eventType == XmlPullParser.END_TAG
+                        && parser.getName().equals(XML_TAG_DESCRIPTOR))) {
+                if (eventType == XmlPullParser.START_TAG) {
+                    switch (parser.getName()) {
+                        case XML_TAG_DESCRIPTOR:
+                            powerComponentId = parser.getAttributeInt(null, XML_ATTR_ID);
+                            name = parser.getAttributeValue(null, XML_ATTR_NAME);
+                            statsArrayLength = parser.getAttributeInt(null,
+                                    XML_ATTR_STATS_ARRAY_LENGTH);
+                            uidStatsArrayLength = parser.getAttributeInt(null,
+                                    XML_ATTR_UID_STATS_ARRAY_LENGTH);
+                            break;
+                        case XML_TAG_EXTRAS:
+                            extras = PersistableBundle.restoreFromXml(parser);
+                            break;
+                    }
+                }
+                eventType = parser.next();
+            }
+            if (powerComponentId == -1) {
+                return null;
+            } else if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
+                return new Descriptor(powerComponentId, name, statsArrayLength, uidStatsArrayLength,
+                        extras);
+            } else if (powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT) {
+                return new Descriptor(powerComponentId, statsArrayLength, uidStatsArrayLength,
+                        extras);
+            } else {
+                Slog.e(TAG, "Unrecognized power component: " + powerComponentId);
+                return null;
+            }
+        }
+
         @Override
         public int hashCode() {
             return Objects.hash(powerComponentId);
@@ -259,7 +338,7 @@
 
             Descriptor descriptor = registry.get(powerComponentId);
             if (descriptor == null) {
-                Log.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId);
+                Slog.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId);
                 return null;
             }
             PowerStats stats = new PowerStats(descriptor);
@@ -274,7 +353,7 @@
                 stats.uidStats.put(uid, uidStats);
             }
             if (parcel.dataPosition() != endPos) {
-                Log.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length
+                Slog.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length
                            + ", actual length: " + (parcel.dataPosition() - startPos));
                 return null;
             }
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 1be916f..8566263 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -290,11 +290,12 @@
     };
     private Consumer<Boolean> mCrossWindowBlurEnabledListener;
 
+    private final WearGestureInterceptionDetector mWearGestureInterceptionDetector;
+
     DecorView(Context context, int featureId, PhoneWindow window,
             WindowManager.LayoutParams params) {
         super(context);
         mFeatureId = featureId;
-
         mShowInterpolator = AnimationUtils.loadInterpolator(context,
                 android.R.interpolator.linear_out_slow_in);
         mHideInterpolator = AnimationUtils.loadInterpolator(context,
@@ -314,6 +315,11 @@
         updateLogTag(params);
 
         mLegacyNavigationBarBackgroundPaint.setColor(Color.BLACK);
+
+        mWearGestureInterceptionDetector =
+                WearGestureInterceptionDetector.isEnabled(context)
+                        ? new WearGestureInterceptionDetector(context, this)
+                        : null;
     }
 
     void setBackgroundFallback(@Nullable Drawable fallbackDrawable) {
@@ -544,6 +550,18 @@
             }
         }
 
+        ViewRootImpl viewRootImpl = getViewRootImpl();
+        if (viewRootImpl != null && mWearGestureInterceptionDetector != null) {
+            boolean wasIntercepting = mWearGestureInterceptionDetector.isIntercepting();
+            boolean intercepting = mWearGestureInterceptionDetector.onInterceptTouchEvent(event);
+            if (wasIntercepting != intercepting) {
+                viewRootImpl.updateDecorViewGestureInterception(intercepting);
+            }
+            if (intercepting) {
+                return true;
+            }
+        }
+
         if (!SWEEP_OPEN_MENU) {
             return false;
         }
diff --git a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
new file mode 100644
index 0000000..6fd5018
--- /dev/null
+++ b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
@@ -0,0 +1,211 @@
+/*
+ * 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.internal.policy;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.TypedArray;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+
+/**
+ * Wear-specific gesture interception detector to be installed at DecorView, for compatibility of
+ * apps depending on legacy SwipeDismissLayout behavior.
+ *
+ * <p>Results of the detector will be used by {@code DecorView} to intercept motion events. The
+ * interception state will also be sent to {@code android.view.ViewRootImpl} and {@code
+ * com.android.server.wm.DisplayContent} through {@code android.view.IWindowSession}.
+ *
+ * <p>SystemUI can register {@code android.view.IDecorViewGestureListener} to listen for the result
+ * of the detector. The result will be valid for between a pair of touch down/up events.
+ */
+public class WearGestureInterceptionDetector {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "WearGestureInterceptionDetector";
+
+    private final DecorView mInstalledDecorView;
+    private final float mTouchSlop;
+    private final float mSwipingStartThreshold;
+    private boolean mSwiping;
+
+    private float mDownX;
+    private float mDownY;
+    private int mActivePointerId;
+    private boolean mDiscardIntercept;
+
+    WearGestureInterceptionDetector(Context context, DecorView installedDecorView) {
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        mInstalledDecorView = installedDecorView;
+        mSwipingStartThreshold = mTouchSlop * 2;
+    }
+
+    /** Check if this gesture interception detector should be enabled. */
+    public static boolean isEnabled(Context context) {
+        PackageManager pm = context.getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+            return false;
+        }
+
+        // Compatibility check for flag that disables legacy SwipeDismissLayout.
+        TypedArray windowAttr =
+                context.obtainStyledAttributes(new int[] {android.R.attr.windowSwipeToDismiss});
+        boolean windowSwipeToDismiss = true;
+        if (windowAttr.getIndexCount() > 0) {
+            windowSwipeToDismiss = windowAttr.getBoolean(0, true);
+        }
+        windowAttr.recycle();
+        return windowSwipeToDismiss;
+    }
+
+    private boolean isPointerIndexValid(MotionEvent ev) {
+        int pointerIndex = ev.findPointerIndex(mActivePointerId);
+        if (pointerIndex == -1) {
+            if (DEBUG) {
+                Log.e(TAG, "Invalid pointer index: ignoring.");
+            }
+            mDiscardIntercept = true;
+            return false;
+        }
+        return true;
+    }
+
+    private void updateSwiping(MotionEvent ev) {
+        if (mSwiping) {
+            return;
+        }
+        float deltaX = ev.getRawX() - mDownX;
+        float deltaY = ev.getRawY() - mDownY;
+        // Check if we have left the touch slop area.
+        if ((deltaX * deltaX) + (deltaY * deltaY) > (mTouchSlop * mTouchSlop)) {
+            mSwiping = deltaX > mSwipingStartThreshold && Math.abs(deltaY) < Math.abs(deltaX);
+        }
+    }
+
+    private void updateDiscardIntercept(MotionEvent ev) {
+        if (!mSwiping) {
+            // Don't look at canScroll until we have passed the touch slop
+            return;
+        }
+        if (mDiscardIntercept) {
+            return;
+        }
+        final boolean checkLeft = mDownX < ev.getRawX();
+        final float x = ev.getX(mActivePointerId);
+        final float y = ev.getY(mActivePointerId);
+        if (canScroll(mInstalledDecorView, false, checkLeft, x, y)) {
+            mDiscardIntercept = true;
+        }
+    }
+
+    /** Resets internal members when canceling. */
+    private void resetMembers() {
+        mDownX = 0;
+        mDownY = 0;
+        mSwiping = false;
+        mDiscardIntercept = false;
+    }
+
+    /** Should we intercept the MotionEvent for system gesture? */
+    public boolean isIntercepting() {
+        return !mDiscardIntercept && mSwiping;
+    }
+
+    /** Tests if the MotionEvent should be intercepted */
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        switch (ev.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                resetMembers();
+                mDownX = ev.getRawX();
+                mDownY = ev.getRawY();
+                mActivePointerId = ev.getPointerId(0);
+                break;
+            case MotionEvent.ACTION_POINTER_DOWN:
+                mActivePointerId = ev.getPointerId(ev.getActionIndex());
+                break;
+            case MotionEvent.ACTION_POINTER_UP:
+                int associatedPointerIndex = ev.getActionIndex();
+                if (ev.getPointerId(associatedPointerIndex) == mActivePointerId) {
+                    // This was our active pointer going up.
+                    // Choose the first available pointer index.
+                    int newActionIndex = associatedPointerIndex == 0 ? 1 : 0;
+                    mActivePointerId = ev.getPointerId(newActionIndex);
+                }
+                break;
+            case MotionEvent.ACTION_MOVE:
+                if (mDiscardIntercept) {
+                    break;
+                }
+                if (!isPointerIndexValid(ev)) {
+                    break;
+                }
+                updateSwiping(ev);
+                updateDiscardIntercept(ev);
+                break;
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                resetMembers();
+                break;
+        }
+        return isIntercepting();
+    }
+
+    /**
+     * Tests scroll-ability within child views of v in the direction of dx.
+     *
+     * @param v View to test for horizontal scroll-ability
+     * @param checkSelf Whether the view v passed should itself be checked for scroll-ability
+     *     (true), or just its children (false).
+     * @param checkLeft Which direction to check? Left = true, right = false.
+     * @param x X coordinate of the active touch point
+     * @param y Y coordinate of the active touch point
+     * @return true if child views of v can be scrolled by delta of dx.
+     */
+    private boolean canScroll(View v, boolean checkSelf, boolean checkLeft, float x, float y) {
+        if (v instanceof ViewGroup) {
+            final ViewGroup group = (ViewGroup) v;
+            final int scrollX = v.getScrollX();
+            final int scrollY = v.getScrollY();
+            final int count = group.getChildCount();
+            for (int i = count - 1; i >= 0; i--) {
+                final View child = group.getChildAt(i);
+
+                if (x + scrollX < child.getLeft()
+                        || x + scrollX >= child.getRight()
+                        || y + scrollY < child.getTop()
+                        || y + scrollY >= child.getBottom()) {
+                    // This child is out of bound, don't bother checking.
+                    continue;
+                }
+
+                // Recursively check until finding the first scrollable or none is scrollable.
+                if (canScroll(
+                        /* view= */ child,
+                        /* checkSelf= */ true,
+                        /* checkLeft= */ checkLeft,
+                        /* x= */ x + scrollX - child.getLeft(),
+                        /* y= */ y + scrollY - child.getTop())) {
+                    return true;
+                }
+            }
+        }
+
+        return checkSelf && v.canScrollHorizontally(checkLeft ? -1 : 1);
+    }
+}
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index ec525f0..4bb7c33 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -91,6 +91,8 @@
     WM_DEBUG_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
             "CoreBackPreview"),
     WM_DEBUG_DREAM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM),
+
+    WM_DEBUG_DIMMER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
     TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
 
     private final boolean mEnabled;
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 9384f41..178c0d0 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -127,6 +127,7 @@
     jfieldID xDpi;
     jfieldID yDpi;
     jfieldID refreshRate;
+    jfieldID vsyncRate;
     jfieldID appVsyncOffsetNanos;
     jfieldID presentationDeadlineNanos;
     jfieldID group;
@@ -469,9 +470,10 @@
     }
 }
 
-static void nativeApplyTransaction(JNIEnv* env, jclass clazz, jlong transactionObj, jboolean sync) {
+static void nativeApplyTransaction(JNIEnv* env, jclass clazz, jlong transactionObj, jboolean sync,
+                                   jboolean oneWay) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
-    transaction->apply(sync);
+    transaction->apply(sync, oneWay);
 }
 
 static void nativeMergeTransaction(JNIEnv* env, jclass clazz,
@@ -961,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,
@@ -1230,6 +1233,7 @@
     env->SetFloatField(object, gDisplayModeClassInfo.yDpi, config.yDpi);
 
     env->SetFloatField(object, gDisplayModeClassInfo.refreshRate, config.refreshRate);
+    env->SetFloatField(object, gDisplayModeClassInfo.vsyncRate, config.vsyncRate);
     env->SetLongField(object, gDisplayModeClassInfo.appVsyncOffsetNanos, config.appVsyncOffset);
     env->SetLongField(object, gDisplayModeClassInfo.presentationDeadlineNanos,
                       config.presentationDeadline);
@@ -2119,7 +2123,7 @@
             (void*)nativeSetDefaultBufferSize},
     {"nativeCreateTransaction", "()J",
             (void*)nativeCreateTransaction },
-    {"nativeApplyTransaction", "(JZ)V",
+    {"nativeApplyTransaction", "(JZZ)V",
             (void*)nativeApplyTransaction },
     {"nativeGetNativeTransactionFinalizer", "()J",
             (void*)nativeGetNativeTransactionFinalizer },
@@ -2178,7 +2182,7 @@
             (void*)nativeSetFrameRate },
     {"nativeSetDefaultFrameRateCompatibility", "(JJI)V",
             (void*)nativeSetDefaultFrameRateCompatibility},
-    {"nativeSetFrameRateCategory", "(JJI)V",
+    {"nativeSetFrameRateCategory", "(JJIZ)V",
             (void*)nativeSetFrameRateCategory},
     {"nativeSetFrameRateSelectionStrategy", "(JJI)V",
             (void*)nativeSetFrameRateSelectionStrategy},
@@ -2393,6 +2397,7 @@
     gDisplayModeClassInfo.xDpi = GetFieldIDOrDie(env, modeClazz, "xDpi", "F");
     gDisplayModeClassInfo.yDpi = GetFieldIDOrDie(env, modeClazz, "yDpi", "F");
     gDisplayModeClassInfo.refreshRate = GetFieldIDOrDie(env, modeClazz, "refreshRate", "F");
+    gDisplayModeClassInfo.vsyncRate = GetFieldIDOrDie(env, modeClazz, "vsyncRate", "F");
     gDisplayModeClassInfo.appVsyncOffsetNanos =
             GetFieldIDOrDie(env, modeClazz, "appVsyncOffsetNanos", "J");
     gDisplayModeClassInfo.presentationDeadlineNanos =
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index 6920211..1f29735 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -55,6 +55,15 @@
     counter->setState(state, timestamp);
 }
 
+static void native_setValues(jlong nativePtr, jint state, jlong longArrayContainerNativePtr) {
+    battery::LongArrayMultiStateCounter *counter =
+            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+    std::vector<uint64_t> *vector =
+            reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
+
+    counter->setValue(state, *vector);
+}
+
 static void native_updateValues(jlong nativePtr, jlong longArrayContainerNativePtr,
                                 jlong timestamp) {
     battery::LongArrayMultiStateCounter *counter =
@@ -210,6 +219,8 @@
         // @CriticalNative
         {"native_setState", "(JIJ)V", (void *)native_setState},
         // @CriticalNative
+        {"native_setValues", "(JIJ)V", (void *)native_setValues},
+        // @CriticalNative
         {"native_updateValues", "(JJJ)V", (void *)native_updateValues},
         // @CriticalNative
         {"native_incrementValues", "(JJJ)V", (void *)native_incrementValues},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e34e423..88b578b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -48,6 +48,7 @@
     <protected-broadcast android:name="android.intent.action.CANCEL_ENABLE_ROLLBACK" />
     <protected-broadcast android:name="android.intent.action.ROLLBACK_COMMITTED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_RESTARTED" />
+    <protected-broadcast android:name="android.intent.action.PACKAGE_UNSTOPPED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_DATA_CLEARED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_FIRST_LAUNCH" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_NEEDS_INTEGRITY_VERIFICATION" />
@@ -2107,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" />
 
@@ -6121,7 +6122,9 @@
         android:protectionLevel="signature|privileged|development|appop|retailDemo" />
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
 
-    <!-- @SystemApi @hide Allows trusted system components to report events to UsageStatsManager -->
+    <!-- @SystemApi @hide
+         @FlaggedApi("backstage_power.report_usage_stats_permission")
+         Allows trusted system components to report events to UsageStatsManager -->
     <permission android:name="android.permission.REPORT_USAGE_STATS"
                 android:protectionLevel="signature|module" />
 
@@ -7232,6 +7235,25 @@
                 android:description="@string/permdesc_fullScreenIntent"
                 android:protectionLevel="normal|appop" />
 
+    <!-- @SystemApi Required for the privileged assistant apps targeting
+         {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
+         that receive voice trigger from a sandboxed {@link HotwordDetectionService}.
+         <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 a sandboxed {@link HotwordDetectionService} or
+         {@link VisualQueryDetectionService}.
+         <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-bn/strings.xml b/core/res/res/values-bn/strings.xml
index a49e547..6f879e4 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -674,7 +674,7 @@
     <string name="device_unlock_notification_name" msgid="2632928999862915709">"ডিভাইস আনলক করুন"</string>
     <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"অন্য কোনওভাবে আনলক করার চেষ্টা করুন"</string>
     <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"আপনার \'ফিঙ্গারপ্রিন্ট\' শনাক্ত করা না গেলে \'ফেস আনলক\' ব্যবহার করুন, যেমন যখন আপনার আঙুল ভিজে থাকে"</string>
-    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"আপনার মুখ শনাক্ত করা না গেলে \'ফিঙ্গারপ্রিন্ট আনলক\' ব্যবহার করুন, যেমন যখন পর্যাপ্ত আলো নেই"</string>
+    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"পর্যাপ্ত আলো না থাকার পরিস্থিতিতে, আপনার মুখ শনাক্ত করা না গেলে \'ফিঙ্গারপ্রিন্ট আনলক\' ব্যবহার করুন"</string>
     <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"ফেস আনলক"</string>
     <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"\'ফেস আনলক\' ফিচার ব্যবহার করার ক্ষেত্রে হওয়া সমস্যা"</string>
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"আপনার ফেস মডেল মুছে দেওয়ার জন্য ট্যাপ করুন এবং তারপরে আবার ফেস যোগ করুন"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index a897170..2150324 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -476,7 +476,7 @@
     <string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"atzitu kokapen-hornitzaileen komando gehigarriak"</string>
     <string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Kokapen-hornitzailearen agindu gehigarriak erabiltzeko baimena ematen die aplikazioei. Horrela, agian aplikazioek GPSaren edo bestelako kokapenaren iturburuen funtzionamenduan eragina izan dezakete."</string>
     <string name="permlab_accessFineLocation" msgid="6426318438195622966">"lortu kokapen zehatza aurreko planoan bakarrik"</string>
-    <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"Abian denean, aplikazioak kokapen zehatza lor dezake kokapen-zerbitzuen bidez. Aplikazioak kokapena lortu ahal izateko, kokapen-zerbitzuek aktibatuta egon behar dute gailuan. Bateria-erabilera areagotzen du horrek."</string>
+    <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"Abian denean, aplikazioak kokapen zehatza lor dezake kokapen-zerbitzuen bidez. Aplikazioak kokapena lortu ahal izateko, kokapen-zerbitzuek aktibatuta egon behar dute gailuan. Agian bateria gehiago erabiliko du."</string>
     <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"atzitu gutxi gorabeherako kokapena aurreko planoan bakarrik"</string>
     <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"Abian denean, aplikazioak gutxi gorabeherako kokapena lor dezake kokapen-zerbitzuen bidez. Aplikazioak kokapena lortu ahal izateko, kokapen-zerbitzuek aktibatuta egon behar dute gailuan."</string>
     <string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"atzitu kokapena atzeko planoan"</string>
@@ -2137,7 +2137,7 @@
     <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"Aplikazioak ez du grabatzeko baimenik, baina baliteke audioa grabatzea USB bidezko gailu horren bidez."</string>
     <string name="accessibility_system_action_home_label" msgid="3234748160850301870">"Pantaila nagusia"</string>
     <string name="accessibility_system_action_back_label" msgid="4205361367345537608">"Atzera"</string>
-    <string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Erabilitako azken aplikazioak"</string>
+    <string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Azkenaldian erabilitako aplikazioak"</string>
     <string name="accessibility_system_action_notifications_label" msgid="6083767351772162010">"Jakinarazpenak"</string>
     <string name="accessibility_system_action_quick_settings_label" msgid="4583900123506773783">"Ezarpen bizkorrak"</string>
     <string name="accessibility_system_action_power_dialog_label" msgid="8095341821683910781">"Piztu edo itzaltzeko leihoa"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 2867516..d7d29a4 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -299,8 +299,8 @@
     <string name="android_system_label" msgid="5974767339591067210">"Système Android"</string>
     <string name="user_owner_label" msgid="8628726904184471211">"Passer au profil personnel"</string>
     <string name="managed_profile_label" msgid="7316778766973512382">"Passer au profil pro"</string>
-    <string name="user_owner_app_label" msgid="1553595155465750298">"Passer au <xliff:g id="APP_NAME">%1$s</xliff:g> personnel"</string>
-    <string name="managed_profile_app_label" msgid="367401088383965725">"Passer au <xliff:g id="APP_NAME">%1$s</xliff:g> professionnel"</string>
+    <string name="user_owner_app_label" msgid="1553595155465750298">"Passer à l\'application <xliff:g id="APP_NAME">%1$s</xliff:g> personnelle"</string>
+    <string name="managed_profile_app_label" msgid="367401088383965725">"Passer à l\'application <xliff:g id="APP_NAME">%1$s</xliff:g> professionnelle"</string>
     <string name="permgrouplab_contacts" msgid="4254143639307316920">"Contacts"</string>
     <string name="permgroupdesc_contacts" msgid="9163927941244182567">"accéder à vos contacts"</string>
     <string name="permgrouplab_location" msgid="1858277002233964394">"Position"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 5a98ef8..d2b6dc8 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -674,7 +674,7 @@
     <string name="device_unlock_notification_name" msgid="2632928999862915709">"기기 잠금 해제"</string>
     <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"다른 잠금 해제 방법 사용"</string>
     <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"손가락에 물기가 있는 등 지문이 인식되지 않을 때는 얼굴 인식 잠금 해제를 사용하세요."</string>
-    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"충분히 밝지 않은 경우 등 얼굴이 인식되지 않을 때는 지문 잠금 해제를 사용하세요."</string>
+    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"주변이 어두운 경우 등 얼굴이 인식되지 않을 때는 지문 잠금 해제를 사용해 보세요."</string>
     <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"얼굴 인식 잠금 해제"</string>
     <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"얼굴 인식 잠금 해제 문제"</string>
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"탭하여 얼굴 모델을 삭제한 후 다시 얼굴을 추가하세요"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 5f3b315..485e9b6 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -674,7 +674,7 @@
     <string name="device_unlock_notification_name" msgid="2632928999862915709">"डिव्हाइस अनलॉक"</string>
     <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"अनलॉक करण्याची दुसरी पद्धत वापरून पहा"</string>
     <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"तुमचे फिंगरप्रिंट ओळखले जात नाही, तेव्हा फेस अनलॉक वापरा, जसे की तुमची बोटे ओली असताना"</string>
-    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"तुमचा चेहरा ओळखला जात नाही, तेव्हा फिंगरप्रिंट अनलॉक वापरा, जसे की पुरेसा प्रकाश नसताना"</string>
+    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"पुरेसा प्रकाश नसणे इत्यादिमुळे तुमचा चेहरा ओळखला जात नाही, अशावेळी फिंगरप्रिंट अनलॉक वापरा"</string>
     <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"फेस अनलॉक"</string>
     <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"फेस अनलॉकसंबंधित समस्या"</string>
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"फेस मॉडेल हटवण्यासाठी टॅप करा, त्यानंतर तुमचा चेहरा पुन्हा जोडा"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 9583c0e5..628627d 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -675,7 +675,7 @@
     <string name="device_unlock_notification_name" msgid="2632928999862915709">"Desbloqueio do dispositivo"</string>
     <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"Tente desbloquear de outra maneira"</string>
     <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"Use o Desbloqueio facial quando sua impressão digital não for reconhecida, como quando seus dedos estiverem molhados"</string>
-    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"Use o Desbloqueio por impressão digital quando seu rosto não for reconhecido, como quando não houver luz suficiente"</string>
+    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"Use o Desbloqueio por impressão digital quando seu rosto não for reconhecido, se estiver escuro, por exemplo"</string>
     <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"Desbloqueio facial"</string>
     <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"Problema com o Desbloqueio facial"</string>
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Toque para excluir seu modelo de rosto e crie um novo"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 9583c0e5..628627d 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -675,7 +675,7 @@
     <string name="device_unlock_notification_name" msgid="2632928999862915709">"Desbloqueio do dispositivo"</string>
     <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"Tente desbloquear de outra maneira"</string>
     <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"Use o Desbloqueio facial quando sua impressão digital não for reconhecida, como quando seus dedos estiverem molhados"</string>
-    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"Use o Desbloqueio por impressão digital quando seu rosto não for reconhecido, como quando não houver luz suficiente"</string>
+    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"Use o Desbloqueio por impressão digital quando seu rosto não for reconhecido, se estiver escuro, por exemplo"</string>
     <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"Desbloqueio facial"</string>
     <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"Problema com o Desbloqueio facial"</string>
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Toque para excluir seu modelo de rosto e crie um novo"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 8573c20c..aa22aea 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -673,8 +673,8 @@
     <string name="fingerprint_icon_content_description" msgid="4741068463175388817">"వేలిముద్ర చిహ్నం"</string>
     <string name="device_unlock_notification_name" msgid="2632928999862915709">"పరికర అన్‌లాక్"</string>
     <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"అన్‌లాక్ చేయడానికి మరొక మార్గాన్ని ట్రై చేయండి"</string>
-    <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"మీ వేలిముద్ర గుర్తించబడనప్పుడు, మీ వేళ్లు తడిగా ఉన్నప్పుడు ఫేస్ అన్‌లాక్‌ను ఉపయోగించండి"</string>
-    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"మీ ఫేస్ గుర్తించబడనప్పుడు, తగినంత వెలుతురు లేనప్పుడు వేలిముద్ర అన్‌లాక్‌ను ఉపయోగించండి"</string>
+    <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"మీ వేళ్లు తడిగా ఉండటం లేక ఇతరత్రా కారణాల వల్ల మీ వేలిముద్రను గుర్తించకపోతే, ఫేస్ అన్‌లాక్‌ను ఉపయోగించండి"</string>
+    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"తగినంత వెలుతురు లేకపోవడం లేక ఇతరత్రా కారణాల వల్ల మీ ఫేస్ గుర్తించబడనప్పుడు, వేలిముద్ర అన్‌లాక్‌ను ఉపయోగించండి"</string>
     <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"ఫేస్ అన్‌లాక్"</string>
     <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"ఫేస్ అన్‌లాక్‌తో సమస్య"</string>
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"ఫేస్ మోడల్‌ను తొలగించడానికి నొక్కండి, ఆపై మీ ముఖాన్ని మళ్లీ జోడించండి"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 936c978b..d34e976 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -675,8 +675,8 @@
     <string name="fingerprint_icon_content_description" msgid="4741068463175388817">"Значок відбитка пальця"</string>
     <string name="device_unlock_notification_name" msgid="2632928999862915709">"Розблокування пристрою"</string>
     <string name="alternative_unlock_setup_notification_title" msgid="6241508547901933544">"Спробуйте інший спосіб розблокування"</string>
-    <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"Розблоковуйте пристрій за допомогою фейс-контролю, коли не вдається розпізнати ваш відбиток пальця (наприклад, коли у вас мокрі пальці)"</string>
-    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"Розблоковуйте пристрій відбитком пальця, коли не вдається розпізнати ваше обличчя (наприклад, коли недостатньо світла)"</string>
+    <string name="alternative_face_setup_notification_content" msgid="3384959224091897331">"Якщо пристрій не розпізнає ваш відбиток пальця (наприклад, коли у вас мокрі руки), використовуйте фейс-контроль"</string>
+    <string name="alternative_fp_setup_notification_content" msgid="7454096947415721639">"Якщо пристрій не розпізнає ваше обличчя (наприклад, коли освітлення погане), використовуйте відбиток пальця"</string>
     <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"Фейс-контроль"</string>
     <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"Сталася помилка з фейсконтролем"</string>
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Натисніть, щоб видалити свою модель обличчя, а потім знову додайте її"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index ccdd945..06ec1dd 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -1891,7 +1891,7 @@
     <string name="zen_mode_duration_hours" msgid="7841806065034711849">"{count,plural, =1{1 小時}other{# 小時}}"</string>
     <string name="zen_mode_duration_hours_short" msgid="3666949653933099065">"{count,plural, =1{1 小時}other{# 小時}}"</string>
     <string name="zen_mode_until_next_day" msgid="1403042784161725038">"直至<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
-    <string name="zen_mode_until" msgid="2250286190237669079">"完成時間:<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+    <string name="zen_mode_until" msgid="2250286190237669079">"結束時間:<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
     <string name="zen_mode_alarm" msgid="7046911727540499275">"直至<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (下一次響鬧)"</string>
     <string name="zen_mode_forever" msgid="740585666364912448">"直至你關閉為止"</string>
     <string name="zen_mode_forever_dnd" msgid="3423201955704180067">"直至你關閉「請勿騷擾」功能"</string>
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/config.xml b/core/res/res/values/config.xml
index 4360c5a..b211ac2 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1026,6 +1026,12 @@
     <!-- Duration, in milliseconds, of the display white balance animated transitions. -->
     <integer name="config_displayWhiteBalanceTransitionTime">3000</integer>
 
+    <!-- Duration, in milliseconds, of the display white balance animated transitions when increasing cct. -->
+    <integer name="config_displayWhiteBalanceTransitionTimeIncrease">1000</integer>
+
+    <!-- Duration, in milliseconds, of the display white balance animated transitions when decreasing cct. -->
+    <integer name="config_displayWhiteBalanceTransitionTimeDecrease">40000</integer>
+
     <!-- Device states where the sensor based rotation values should be reversed around the Z axis
          for the default display.
          TODO(b/265312193): Remove this workaround when this bug is fixed.-->
@@ -1151,6 +1157,14 @@
     <!-- Allows activities to be launched on a long press on power during device setup. -->
     <bool name="config_allowStartActivityForLongPressOnPowerInSetup">false</bool>
 
+    <!-- Control the behavior when the user short presses the settings button.
+            0 - Nothing
+            1 - Launch notification panel
+         This needs to match the constants in
+         com/android/server/policy/PhoneWindowManager.java
+    -->
+    <integer name="config_shortPressOnSettingsBehavior">0</integer>
+
     <!-- Control the behavior when the user short presses the power button.
             0 - Nothing
             1 - Go to sleep (doze)
diff --git a/core/res/res/values/config_battery_stats.xml b/core/res/res/values/config_battery_stats.xml
index 8fb48bc..e42962c 100644
--- a/core/res/res/values/config_battery_stats.xml
+++ b/core/res/res/values/config_battery_stats.xml
@@ -31,4 +31,13 @@
     is a relatively expensive operation, this throttle period may need to be adjusted for low-power
     devices-->
     <integer name="config_defaultPowerStatsThrottlePeriodCpu">60000</integer>
+
+    <!-- PowerStats aggregation period in milliseconds. This is the interval at which the power
+    stats aggregation procedure is performed and the results stored in PowerStatsStore. -->
+    <integer name="config_powerStatsAggregationPeriod">14400000</integer>
+
+    <!-- PowerStats aggregation span duration in milliseconds. This is the length of battery
+    history time for every aggregated power stats span that is stored stored in PowerStatsStore.
+    It should not be larger than config_powerStatsAggregationPeriod (but it can be the same) -->
+    <integer name="config_aggregatedPowerStatsSpanDuration">3600000</integer>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index fac6aac..a2a4e34f 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,16 @@
     <!-- 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>
+
+    <!-- Title of cable don't support displays notifications. [CHAR LIMIT=NONE] -->
+    <string name="connected_display_cable_dont_support_displays_notification_title">Cable may not support displays</string>
+    <!-- Content of cable don't support displays notification. [CHAR LIMIT=NONE] -->
+    <string name="connected_display_cable_dont_support_displays_notification_content">Your USB-C cable may not connect to displays properly</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 7f30695..76744ea 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1816,6 +1816,7 @@
   <java-symbol type="integer" name="config_lidNavigationAccessibility" />
   <java-symbol type="integer" name="config_lidOpenRotation" />
   <java-symbol type="integer" name="config_longPressOnHomeBehavior" />
+  <java-symbol type="integer" name="config_shortPressOnSettingsBehavior" />
   <java-symbol type="layout" name="global_actions" />
   <java-symbol type="layout" name="global_actions_item" />
   <java-symbol type="layout" name="global_actions_silent_mode" />
@@ -1996,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" />
@@ -3485,6 +3487,8 @@
   <java-symbol type="array" name="config_displayWhiteBalanceDisplaySteps" />
   <java-symbol type="bool" name="config_displayWhiteBalanceLightModeAllowed" />
   <java-symbol type="integer" name="config_displayWhiteBalanceTransitionTime" />
+  <java-symbol type="integer" name="config_displayWhiteBalanceTransitionTimeIncrease" />
+  <java-symbol type="integer" name="config_displayWhiteBalanceTransitionTimeDecrease" />
 
   <!-- Device states where the sensor based rotation values should be reversed around the Z axis
        for the default display.
@@ -3810,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" />
@@ -5063,6 +5068,10 @@
   <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="connected_display_cable_dont_support_displays_notification_title"/>
+  <java-symbol type="string" name="connected_display_cable_dont_support_displays_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"/>
@@ -5115,6 +5124,8 @@
   <java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
   <java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
   <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodCpu" />
+  <java-symbol type="integer" name="config_powerStatsAggregationPeriod" />
+  <java-symbol type="integer" name="config_aggregatedPowerStatsSpanDuration" />
 
   <java-symbol name="materialColorOnSecondaryFixedVariant" type="attr"/>
   <java-symbol name="materialColorOnTertiaryFixedVariant" type="attr"/>
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 824f591..75a7231 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -610,8 +610,6 @@
     @Test
     public void cancel() throws Exception {
         openAidlClients(/* numClients= */ 1);
-        ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
-        mTunerSessions[0].tune(initialSel);
 
         mTunerSessions[0].cancel();
 
@@ -621,9 +619,6 @@
     @Test
     public void cancel_forNonCurrentUser_doesNotCancel() throws Exception {
         openAidlClients(/* numClients= */ 1);
-        ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
-        mTunerSessions[0].tune(initialSel);
-        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(any());
         doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
 
         mTunerSessions[0].cancel();
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
index 3b9d7ba..6edfa02 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
@@ -540,8 +540,6 @@
     @Test
     public void cancel() throws Exception {
         openAidlClients(/* numClients= */ 1);
-        ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
-        mTunerSessions[0].tune(initialSel);
 
         mTunerSessions[0].cancel();
 
@@ -551,8 +549,6 @@
     @Test
     public void cancel_forNonCurrentUser_doesNotCancel() throws Exception {
         openAidlClients(/* numClients= */ 1);
-        ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
-        mTunerSessions[0].tune(initialSel);
         doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
 
         mTunerSessions[0].cancel();
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 81dab08..2993a0e 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -64,6 +64,7 @@
         "servicestests-utils",
         "device-time-shell-utils",
         "testables",
+        "com.android.text.flags-aconfig-java",
     ],
 
     libs: [
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/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index a8a5059..3f78396 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -16,6 +16,8 @@
 
 package android.graphics;
 
+import static com.android.text.flags.Flags.FLAG_CUSTOM_LOCALE_FALLBACK;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -30,6 +32,9 @@
 import android.graphics.fonts.SystemFonts;
 import android.graphics.text.PositionedGlyphs;
 import android.graphics.text.TextRunShaper;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.text.FontConfig;
 import android.util.ArrayMap;
 
@@ -39,6 +44,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParserException;
@@ -107,6 +113,10 @@
         GLYPH_2EM_WIDTH = paint.measureText("a");
     }
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Before
     public void setUp() {
         final AssetManager am =
@@ -877,6 +887,130 @@
         assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
     }
 
+    private static void assertA3emFontIsUsed(Typeface typeface) {
+        final Paint paint = new Paint();
+        assertNotNull(typeface);
+        paint.setTypeface(typeface);
+        assertTrue("a3em font must be used", GLYPH_3EM_WIDTH == paint.measureText("a")
+                && GLYPH_1EM_WIDTH == paint.measureText("b")
+                && GLYPH_1EM_WIDTH == paint.measureText("c"));
+    }
+
+    private static void assertB3emFontIsUsed(Typeface typeface) {
+        final Paint paint = new Paint();
+        assertNotNull(typeface);
+        paint.setTypeface(typeface);
+        assertTrue("b3em font must be used", GLYPH_1EM_WIDTH == paint.measureText("a")
+                && GLYPH_3EM_WIDTH == paint.measureText("b")
+                && GLYPH_1EM_WIDTH == paint.measureText("c"));
+    }
+
+    private static String getBaseXml(String font, String lang) {
+        final String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+                + "<familyset>"
+                + "  <family>"
+                + "    <font weight='400' style='normal'>no_coverage.ttf</font>"
+                + "  </family>"
+                + "  <family name='named-family'>"
+                + "    <font weight='400' style='normal'>no_coverage.ttf</font>"
+                + "  </family>"
+                + "  <family lang='%s'>"
+                + "    <font weight='400' style='normal'>%s</font>"
+                + "  </family>"
+                + "</familyset>";
+        return String.format(xml, lang, font);
+    }
+
+    private static String getCustomizationXml(String font, String op, String lang) {
+        final String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+                + "<fonts-modification version='1'>"
+                + "  <family customizationType='new-locale-family' operation='%s' lang='%s'>"
+                + "    <font weight='400' style='normal' fallbackFor='named-family'>%s</font>"
+                + "  </family>"
+                + "</fonts-modification>";
+        return String.format(xml, op, lang, font);
+    }
+
+    @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+    @Test
+    public void testBuildSystemFallback__Customization_locale_prepend() {
+        final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
+        final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+
+        buildSystemFallback(
+                getBaseXml("a3em.ttf", "ja-JP"),
+                getCustomizationXml("b3em.ttf", "prepend", "ja-JP"),
+                fontMap, fallbackMap);
+        Typeface typeface = fontMap.get("named-family");
+
+        // operation "prepend" places font before the original font, thus b3em is used.
+        assertB3emFontIsUsed(typeface);
+    }
+
+    @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+    @Test
+    public void testBuildSystemFallback__Customization_locale_replace() {
+        final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
+        final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+
+        buildSystemFallback(
+                getBaseXml("a3em.ttf", "ja-JP"),
+                getCustomizationXml("b3em.ttf", "replace", "ja-JP"),
+                fontMap, fallbackMap);
+        Typeface typeface = fontMap.get("named-family");
+
+        // operation "replace" removes the original font, thus b3em font is used.
+        assertB3emFontIsUsed(typeface);
+    }
+
+    @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+    @Test
+    public void testBuildSystemFallback__Customization_locale_append() {
+        final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
+        final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+
+        buildSystemFallback(
+                getBaseXml("a3em.ttf", "ja-JP"),
+                getCustomizationXml("b3em.ttf", "append", "ja-JP"),
+                fontMap, fallbackMap);
+        Typeface typeface = fontMap.get("named-family");
+
+        // operation "append" comes next to the original font, so the original "a3em" is used.
+        assertA3emFontIsUsed(typeface);
+    }
+
+    @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+    @Test
+    public void testBuildSystemFallback__Customization_locale_ScriptMismatch() {
+        final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
+        final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+
+        buildSystemFallback(
+                getBaseXml("a3em.ttf", "ja-JP"),
+                getCustomizationXml("b3em.ttf", "replace", "ko-KR"),
+                fontMap, fallbackMap);
+        Typeface typeface = fontMap.get("named-family");
+
+        // Since the script doesn't match, the customization is ignored.
+        assertA3emFontIsUsed(typeface);
+    }
+
+    @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+    @Test
+    public void testBuildSystemFallback__Customization_locale_SubscriptMatch() {
+        final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
+        final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+
+        buildSystemFallback(
+                getBaseXml("a3em.ttf", "ja-JP"),
+                getCustomizationXml("b3em.ttf", "replace", "ko-Hani-KR"),
+                fontMap, fallbackMap);
+        Typeface typeface = fontMap.get("named-family");
+
+        // Hani script is supported by Japanese, Jpan.
+        assertB3emFontIsUsed(typeface);
+    }
+
     @Test(expected = IllegalArgumentException.class)
     public void testBuildSystemFallback__Customization_new_named_family_no_name_exception() {
         final String oemXml = "<?xml version='1.0' encoding='UTF-8'?>"
@@ -902,7 +1036,6 @@
         readFontCustomization(oemXml);
     }
 
-
     @Test
     public void testBuildSystemFallback_UpdatableFont() {
         final String xml = "<?xml version='1.0' encoding='UTF-8'?>"
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/service/notification/NotificationRankingUpdateTest.java b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
index 55ded9c..517aeae 100644
--- a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
+++ b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
@@ -16,6 +16,10 @@
 
 package android.service.notification;
 
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
+
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM;
 
 import static junit.framework.Assert.assertEquals;
@@ -24,20 +28,40 @@
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.Mockito.spy;
+
+import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.os.Bundle;
 import android.os.Parcel;
+import android.os.SharedMemory;
+import android.testing.TestableContext;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.Flag;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @SmallTest
 @RunWith(Parameterized.class)
 public class NotificationRankingUpdateTest {
@@ -56,16 +80,368 @@
     @Parameterized.Parameter
     public boolean mRankingUpdateAshmem;
 
+    @Rule
+    public TestableContext mContext =
+            spy(new TestableContext(InstrumentationRegistry.getContext(), null));
+
+    protected TestableContext getContext() {
+        return mContext;
+    }
+
+    public static String[] mKeys = new String[] { "key", "key1", "key2", "key3", "key4"};
+
+    /**
+     * Creates a NotificationRankingUpdate with prepopulated Ranking entries
+     * @param context A testable context, used for PendingIntent creation
+     * @return The NotificationRankingUpdate to be used as test data
+     */
+    public static NotificationRankingUpdate generateUpdate(TestableContext context) {
+        NotificationListenerService.Ranking[] rankings =
+                new NotificationListenerService.Ranking[mKeys.length];
+        for (int i = 0; i < mKeys.length; i++) {
+            final String key = mKeys[i];
+            NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
+            ranking.populate(
+                    key,
+                    i,
+                    !isIntercepted(i),
+                    getVisibilityOverride(i),
+                    getSuppressedVisualEffects(i),
+                    getImportance(i),
+                    getExplanation(key),
+                    getOverrideGroupKey(key),
+                    getChannel(key, i),
+                    getPeople(key, i),
+                    getSnoozeCriteria(key, i),
+                    getShowBadge(i),
+                    getUserSentiment(i),
+                    getHidden(i),
+                    lastAudiblyAlerted(i),
+                    getNoisy(i),
+                    getSmartActions(key, i, context),
+                    getSmartReplies(key, i),
+                    canBubble(i),
+                    isTextChanged(i),
+                    isConversation(i),
+                    getShortcutInfo(i),
+                    getRankingAdjustment(i),
+                    isBubble(i),
+                    getProposedImportance(i),
+                    hasSensitiveContent(i)
+            );
+            rankings[i] = ranking;
+        }
+        return new NotificationRankingUpdate(rankings);
+    }
+
+    /**
+     * Produces a visibility override value based on the provided index.
+     */
+    public static int getVisibilityOverride(int index) {
+        return index * 9;
+    }
+
+    /**
+     * Produces a group key based on the provided key.
+     */
+    public static String getOverrideGroupKey(String key) {
+        return key + key;
+    }
+
+    /**
+     * Produces a boolean that can be used to represent isIntercepted, based on the provided index.
+     */
+    public static boolean isIntercepted(int index) {
+        return index % 2 == 0;
+    }
+
+    /**
+     * Produces a suppressed visual effects value based on the provided index
+     */
+    public static int getSuppressedVisualEffects(int index) {
+        return index * 2;
+    }
+
+    /**
+     * Produces an importance value, based on the provided index
+     */
+    public static int getImportance(int index) {
+        return index;
+    }
+
+    /**
+     * Produces an explanation value, based on the provided key
+     */
+    public static String getExplanation(String key) {
+        return key + "explain";
+    }
+
+    /**
+     * Produces a notification channel, based on the provided key and index
+     */
+    public static NotificationChannel getChannel(String key, int index) {
+        return new NotificationChannel(key, key, getImportance(index));
+    }
+
+    /**
+     * Produces a boolean that can be used to represent showBadge, based on the provided index
+     */
+    public static boolean getShowBadge(int index) {
+        return index % 3 == 0;
+    }
+
+    /**
+     * Produces a user sentiment value, based on the provided index
+     */
+    public static int getUserSentiment(int index) {
+        switch(index % 3) {
+            case 0:
+                return USER_SENTIMENT_NEGATIVE;
+            case 1:
+                return USER_SENTIMENT_NEUTRAL;
+            case 2:
+                return USER_SENTIMENT_POSITIVE;
+        }
+        return USER_SENTIMENT_NEUTRAL;
+    }
+
+    /**
+     * Produces a boolean that can be used to represent "hidden," based on the provided index.
+     */
+    public static boolean getHidden(int index) {
+        return index % 2 == 0;
+    }
+
+    /**
+     * Produces a long to represent lastAudiblyAlerted based on the provided index.
+     */
+    public static long lastAudiblyAlerted(int index) {
+        return index * 2000L;
+    }
+
+    /**
+     * Produces a boolean that can be used to represent "noisy," based on the provided index.
+     */
+    public static boolean getNoisy(int index) {
+        return index < 1;
+    }
+
+    /**
+     * Produces strings that can be used to represent people, based on the provided key and index.
+     */
+    public static ArrayList<String> getPeople(String key, int index) {
+        ArrayList<String> people = new ArrayList<>();
+        for (int i = 0; i < index; i++) {
+            people.add(i + key);
+        }
+        return people;
+    }
+
+    /**
+     * Produces a number of snoozeCriteria, based on the provided key and index.
+     */
+    public static ArrayList<SnoozeCriterion> getSnoozeCriteria(String key, int index) {
+        ArrayList<SnoozeCriterion> snooze = new ArrayList<>();
+        for (int i = 0; i < index; i++) {
+            snooze.add(new SnoozeCriterion(key + i, getExplanation(key), key));
+        }
+        return snooze;
+    }
+
+    /**
+     * Produces a list of Actions which can be used to represent smartActions.
+     * These actions are built from pending intents with intent titles based on the provided
+     * key, and ids based on the provided index.
+     */
+    public static ArrayList<Notification.Action> getSmartActions(String key,
+                                                                 int index,
+                                                                 TestableContext context) {
+        ArrayList<Notification.Action> actions = new ArrayList<>();
+        for (int i = 0; i < index; i++) {
+            PendingIntent intent = PendingIntent.getBroadcast(
+                    context,
+                    index /*requestCode*/,
+                    new Intent("ACTION_" + key),
+                    PendingIntent.FLAG_IMMUTABLE /*flags*/);
+            actions.add(new Notification.Action.Builder(null /*icon*/, key, intent).build());
+        }
+        return actions;
+    }
+
+    /**
+     * Produces index number of "smart replies," all based on the provided key and index
+     */
+    public static ArrayList<CharSequence> getSmartReplies(String key, int index) {
+        ArrayList<CharSequence> choices = new ArrayList<>();
+        for (int i = 0; i < index; i++) {
+            choices.add("choice_" + key + "_" + i);
+        }
+        return choices;
+    }
+
+    /**
+     * Produces a boolean that can be  used to represent canBubble, based on the provided index
+     */
+    public static boolean canBubble(int index) {
+        return index % 4 == 0;
+    }
+
+    /**
+     * Produces a boolean that can be used to represent isTextChanged, based on the provided index.
+     */
+    public static boolean isTextChanged(int index) {
+        return index % 4 == 0;
+    }
+
+    /**
+     * Produces a boolean that can be used to represent isConversation, based on the provided index.
+     */
+    public static boolean isConversation(int index) {
+        return index % 4 == 0;
+    }
+
+    /**
+     * Produces a ShortcutInfo value based on the provided index.
+     */
+    public static ShortcutInfo getShortcutInfo(int index) {
+        ShortcutInfo si = new ShortcutInfo(
+                index, String.valueOf(index), "packageName", new ComponentName("1", "1"), null,
+                "title", 0, "titleResName", "text", 0, "textResName",
+                "disabledMessage", 0, "disabledMessageResName",
+                null, null, 0, null, 0, 0,
+                0, "iconResName", "bitmapPath", null, 0,
+                null, null, null, null);
+        return si;
+    }
+
+    /**
+     * Produces a rankingAdjustment value, based on the provided index.
+     */
+    public static int getRankingAdjustment(int index) {
+        return index % 3 - 1;
+    }
+
+    /**
+     * Produces a proposedImportance, based on the provided index.
+     */
+    public static int getProposedImportance(int index) {
+        return index % 5 - 1;
+    }
+
+    /**
+     * Produces a boolean that can be used to represent hasSensitiveContent, based on the provided
+     * index.
+     */
+    public static boolean hasSensitiveContent(int index) {
+        return index % 3 == 0;
+    }
+
+    /**
+     * Produces a boolean that can be used to represent isBubble, based on the provided index.
+     */
+    public static boolean isBubble(int index) {
+        return index % 4 == 0;
+    }
+
+    /**
+     * Checks that each of the pairs of actions in the two provided lists has identical titles,
+     * and that the lists have the same number of elements.
+     */
+    public void assertActionsEqual(
+            List<Notification.Action> expecteds, List<Notification.Action> actuals) {
+        Assert.assertEquals(expecteds.size(), actuals.size());
+        for (int i = 0; i < expecteds.size(); i++) {
+            Notification.Action expected = expecteds.get(i);
+            Notification.Action actual = actuals.get(i);
+            Assert.assertEquals(expected.title.toString(), actual.title.toString());
+        }
+    }
+
+    /**
+     * Checks that all subelements of the provided NotificationRankingUpdates are equal.
+     */
+    public void detailedAssertEquals(NotificationRankingUpdate a, NotificationRankingUpdate b) {
+        detailedAssertEquals(a.getRankingMap(), b.getRankingMap());
+    }
+
+    /**
+     * Checks that all subelements of the provided Ranking objects are equal.
+     */
+    public void detailedAssertEquals(String comment, NotificationListenerService.Ranking a,
+                                     NotificationListenerService.Ranking b) {
+        Assert.assertEquals(comment, a.getKey(), b.getKey());
+        Assert.assertEquals(comment, a.getRank(), b.getRank());
+        Assert.assertEquals(comment, a.matchesInterruptionFilter(), b.matchesInterruptionFilter());
+        Assert.assertEquals(comment, a.getLockscreenVisibilityOverride(),
+                b.getLockscreenVisibilityOverride());
+        Assert.assertEquals(comment, a.getSuppressedVisualEffects(),
+                b.getSuppressedVisualEffects());
+        Assert.assertEquals(comment, a.getImportance(), b.getImportance());
+        Assert.assertEquals(comment, a.getImportanceExplanation(), b.getImportanceExplanation());
+        Assert.assertEquals(comment, a.getOverrideGroupKey(), b.getOverrideGroupKey());
+        Assert.assertEquals(comment, a.getChannel().toString(), b.getChannel().toString());
+        Assert.assertEquals(comment, a.getAdditionalPeople(), b.getAdditionalPeople());
+        Assert.assertEquals(comment, a.getSnoozeCriteria(), b.getSnoozeCriteria());
+        Assert.assertEquals(comment, a.canShowBadge(), b.canShowBadge());
+        Assert.assertEquals(comment, a.getUserSentiment(), b.getUserSentiment());
+        Assert.assertEquals(comment, a.isSuspended(), b.isSuspended());
+        Assert.assertEquals(comment, a.getLastAudiblyAlertedMillis(),
+                b.getLastAudiblyAlertedMillis());
+        Assert.assertEquals(comment, a.isNoisy(), b.isNoisy());
+        Assert.assertEquals(comment, a.getSmartReplies(), b.getSmartReplies());
+        Assert.assertEquals(comment, a.canBubble(), b.canBubble());
+        Assert.assertEquals(comment, a.isConversation(), b.isConversation());
+        if (a.getConversationShortcutInfo() != null && b.getConversationShortcutInfo() != null) {
+            Assert.assertEquals(comment, a.getConversationShortcutInfo().getId(),
+                    b.getConversationShortcutInfo().getId());
+        } else {
+            // One or both must be null, so we can check for equality.
+            Assert.assertEquals(a.getConversationShortcutInfo(), b.getConversationShortcutInfo());
+        }
+        assertActionsEqual(a.getSmartActions(), b.getSmartActions());
+        Assert.assertEquals(a.getProposedImportance(), b.getProposedImportance());
+        Assert.assertEquals(a.hasSensitiveContent(), b.hasSensitiveContent());
+    }
+
+    /**
+     * Checks that the two RankingMaps have identical keys, and that each Ranking object for
+     * each of those keys is identical.
+     */
+    public void detailedAssertEquals(NotificationListenerService.RankingMap a,
+                                     NotificationListenerService.RankingMap b) {
+        NotificationListenerService.Ranking arank = new NotificationListenerService.Ranking();
+        NotificationListenerService.Ranking brank = new NotificationListenerService.Ranking();
+        assertArrayEquals(a.getOrderedKeys(), b.getOrderedKeys());
+        for (String key : a.getOrderedKeys()) {
+            a.getRanking(key, arank);
+            b.getRanking(key, brank);
+            detailedAssertEquals("ranking for key <" + key + ">", arank, brank);
+        }
+    }
+
     @Before
     public void setUp() {
         mNotificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "test channel",
                 NotificationManager.IMPORTANCE_DEFAULT);
 
-        SystemUiSystemPropertiesFlags.TEST_RESOLVER = flag -> {
-            if (flag.mSysPropKey.equals(RANKING_UPDATE_ASHMEM.mSysPropKey)) {
-                return mRankingUpdateAshmem;
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = new FlagResolver() {
+            @Override
+            public boolean isEnabled(Flag flag) {
+                if (flag.mSysPropKey.equals(RANKING_UPDATE_ASHMEM.mSysPropKey)) {
+                    return mRankingUpdateAshmem;
+                }
+                return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag);
             }
-            return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag);
+
+            @Override
+            public int getIntValue(Flag flag) {
+                return 0;
+            }
+
+            @Override
+            public String getStringValue(Flag flag) {
+                return null;
+            }
         };
     }
 
@@ -74,8 +450,13 @@
         SystemUiSystemPropertiesFlags.TEST_RESOLVER = null;
     }
 
-    public NotificationListenerService.Ranking createTestRanking(String key, int rank) {
+    /**
+     * Creates a mostly empty Test Ranking object with the specified key, rank, and smartActions.
+     */
+    public NotificationListenerService.Ranking createEmptyTestRanking(
+            String key, int rank, ArrayList<Notification.Action> actions) {
         NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
+
         ranking.populate(
                 /* key= */ key,
                 /* rank= */ rank,
@@ -93,7 +474,7 @@
                 /* hidden= */ false,
                 /* lastAudiblyAlertedMs= */ -1,
                 /* noisy= */ false,
-                /* smartActions= */ null,
+                /* smartActions= */ actions,
                 /* smartReplies= */ null,
                 /* canBubble= */ false,
                 /* isTextChanged= */ false,
@@ -107,54 +488,111 @@
         return ranking;
     }
 
+    // Tests parceling of NotificationRankingUpdate, and by extension, RankingMap and Ranking.
     @Test
-    public void testRankingUpdate_rankingConstructor() {
-        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
-        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
-                new NotificationListenerService.Ranking[]{ranking});
-
-        NotificationListenerService.RankingMap retrievedRankings = rankingUpdate.getRankingMap();
-        NotificationListenerService.Ranking retrievedRanking =
-                new NotificationListenerService.Ranking();
-        assertTrue(retrievedRankings.getRanking(TEST_KEY, retrievedRanking));
-        assertEquals(123, retrievedRanking.getRank());
-    }
-
-    @Test
-    public void testRankingUpdate_parcelConstructor() {
-        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
-        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
-                new NotificationListenerService.Ranking[]{ranking});
-
-        Parcel parceledRankingUpdate = Parcel.obtain();
-        rankingUpdate.writeToParcel(parceledRankingUpdate, 0);
-        parceledRankingUpdate.setDataPosition(0);
-
-        NotificationRankingUpdate retrievedRankingUpdate = new NotificationRankingUpdate(
-                parceledRankingUpdate);
-
-        NotificationListenerService.RankingMap retrievedRankings =
-                retrievedRankingUpdate.getRankingMap();
-        assertNotNull(retrievedRankings);
+    public void testRankingUpdate_parcel() {
+        NotificationRankingUpdate nru = generateUpdate(getContext());
+        Parcel parcel = Parcel.obtain();
+        nru.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        NotificationRankingUpdate nru1 = NotificationRankingUpdate.CREATOR.createFromParcel(parcel);
         // The rankingUpdate file descriptor is only non-null in the new path.
         if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
                 SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
-            assertTrue(retrievedRankingUpdate.isFdNotNullAndClosed());
+            assertTrue(nru1.isFdNotNullAndClosed());
         }
-        NotificationListenerService.Ranking retrievedRanking =
-                new NotificationListenerService.Ranking();
-        assertTrue(retrievedRankings.getRanking(TEST_KEY, retrievedRanking));
-        assertEquals(123, retrievedRanking.getRank());
-        assertTrue(retrievedRankingUpdate.equals(rankingUpdate));
-        parceledRankingUpdate.recycle();
+        detailedAssertEquals(nru, nru1);
+        parcel.recycle();
+    }
+
+    // Tests parceling of RankingMap and RankingMap.equals
+    @Test
+    public void testRankingMap_parcel() {
+        NotificationListenerService.RankingMap rmap = generateUpdate(getContext()).getRankingMap();
+        Parcel parcel = Parcel.obtain();
+        rmap.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        NotificationListenerService.RankingMap rmap1 =
+                NotificationListenerService.RankingMap.CREATOR.createFromParcel(parcel);
+
+        detailedAssertEquals(rmap, rmap1);
+        Assert.assertEquals(rmap, rmap1);
+        parcel.recycle();
+    }
+
+    // Tests parceling of Ranking and Ranking.equals
+    @Test
+    public void testRanking_parcel() {
+        NotificationListenerService.Ranking ranking =
+                generateUpdate(getContext()).getRankingMap().getRawRankingObject(mKeys[0]);
+        Parcel parcel = Parcel.obtain();
+        ranking.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        NotificationListenerService.Ranking ranking1 =
+                new NotificationListenerService.Ranking(parcel);
+        detailedAssertEquals("rankings differ: ", ranking, ranking1);
+        Assert.assertEquals(ranking, ranking1);
+        parcel.recycle();
+    }
+
+    // Tests NotificationRankingUpdate.equals(), and by extension, RankingMap and Ranking.
+    @Test
+    public void testRankingUpdate_equals_legacy() {
+        NotificationRankingUpdate nru = generateUpdate(getContext());
+        NotificationRankingUpdate nru2 = generateUpdate(getContext());
+        detailedAssertEquals(nru, nru2);
+        Assert.assertEquals(nru, nru2);
+        NotificationListenerService.Ranking tweak =
+                nru2.getRankingMap().getRawRankingObject(mKeys[0]);
+        tweak.populate(
+                tweak.getKey(),
+                tweak.getRank(),
+                !tweak.matchesInterruptionFilter(), // note the inversion here!
+                tweak.getLockscreenVisibilityOverride(),
+                tweak.getSuppressedVisualEffects(),
+                tweak.getImportance(),
+                tweak.getImportanceExplanation(),
+                tweak.getOverrideGroupKey(),
+                tweak.getChannel(),
+                (ArrayList) tweak.getAdditionalPeople(),
+                (ArrayList) tweak.getSnoozeCriteria(),
+                tweak.canShowBadge(),
+                tweak.getUserSentiment(),
+                tweak.isSuspended(),
+                tweak.getLastAudiblyAlertedMillis(),
+                tweak.isNoisy(),
+                (ArrayList) tweak.getSmartActions(),
+                (ArrayList) tweak.getSmartReplies(),
+                tweak.canBubble(),
+                tweak.isTextChanged(),
+                tweak.isConversation(),
+                tweak.getConversationShortcutInfo(),
+                tweak.getRankingAdjustment(),
+                tweak.isBubble(),
+                tweak.getProposedImportance(),
+                tweak.hasSensitiveContent()
+        );
+        assertNotEquals(nru, nru2);
+    }
+
+    @Test
+    public void testRankingUpdate_rankingConstructor() {
+        NotificationRankingUpdate nru = generateUpdate(getContext());
+        NotificationRankingUpdate constructedNru = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{
+                        nru.getRankingMap().getRawRankingObject(mKeys[0]),
+                        nru.getRankingMap().getRawRankingObject(mKeys[1]),
+                        nru.getRankingMap().getRawRankingObject(mKeys[2]),
+                        nru.getRankingMap().getRawRankingObject(mKeys[3]),
+                        nru.getRankingMap().getRawRankingObject(mKeys[4])
+                });
+
+        detailedAssertEquals(nru, constructedNru);
     }
 
     @Test
     public void testRankingUpdate_emptyParcelInCheck() {
-        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
-        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
-                new NotificationListenerService.Ranking[]{ranking});
-
+        NotificationRankingUpdate rankingUpdate = generateUpdate(getContext());
         Parcel parceledRankingUpdate = Parcel.obtain();
         rankingUpdate.writeToParcel(parceledRankingUpdate, 0);
 
@@ -163,37 +601,119 @@
         NotificationRankingUpdate retrievedRankingUpdate = new NotificationRankingUpdate(
                 parceledRankingUpdate);
         assertNull(retrievedRankingUpdate.getRankingMap());
+        parceledRankingUpdate.recycle();
     }
 
     @Test
     public void testRankingUpdate_describeContents() {
-        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
-        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
-                new NotificationListenerService.Ranking[]{ranking});
+        NotificationRankingUpdate rankingUpdate = generateUpdate(getContext());
         assertEquals(0, rankingUpdate.describeContents());
     }
 
     @Test
     public void testRankingUpdate_equals() {
-        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
+        NotificationListenerService.Ranking ranking = createEmptyTestRanking(TEST_KEY, 123, null);
         NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
                 new NotificationListenerService.Ranking[]{ranking});
-        // Reflexive equality.
-        assertTrue(rankingUpdate.equals(rankingUpdate));
-        // Null or wrong class inequality.
+        // Reflexive equality, including handling nulls properly
+        detailedAssertEquals(rankingUpdate, rankingUpdate);
+        // Null or wrong class inequality
         assertFalse(rankingUpdate.equals(null));
         assertFalse(rankingUpdate.equals(ranking));
 
-        // Different ranking contents inequality.
-        NotificationListenerService.Ranking ranking2 = createTestRanking(TEST_KEY, 456);
+        // Different rank inequality
+        NotificationListenerService.Ranking ranking2 = createEmptyTestRanking(TEST_KEY, 456, null);
         NotificationRankingUpdate rankingUpdate2 = new NotificationRankingUpdate(
                 new NotificationListenerService.Ranking[]{ranking2});
         assertFalse(rankingUpdate.equals(rankingUpdate2));
 
-        // Same ranking contents equality.
-        ranking2 = createTestRanking(TEST_KEY, 123);
+        // Different key inequality
+        ranking2 = createEmptyTestRanking(TEST_KEY + "DIFFERENT", 123, null);
         rankingUpdate2 = new NotificationRankingUpdate(
                 new NotificationListenerService.Ranking[]{ranking2});
-        assertTrue(rankingUpdate.equals(rankingUpdate2));
+        assertFalse(rankingUpdate.equals(rankingUpdate2));
+    }
+
+    @Test
+    public void testRankingUpdate_writesSmartActionToParcel() {
+        if (!mRankingUpdateAshmem) {
+            return;
+        }
+        ArrayList<Notification.Action> actions = new ArrayList<>();
+        PendingIntent intent = PendingIntent.getBroadcast(
+                getContext(),
+                0 /*requestCode*/,
+                new Intent("ACTION_" + TEST_KEY),
+                PendingIntent.FLAG_IMMUTABLE /*flags*/);
+        actions.add(new Notification.Action.Builder(null /*icon*/, TEST_KEY, intent).build());
+
+        NotificationListenerService.Ranking ranking =
+                createEmptyTestRanking(TEST_KEY, 123, actions);
+        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking});
+
+        Parcel parcel = Parcel.obtain();
+        rankingUpdate.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        SharedMemory fd = parcel.readParcelable(getClass().getClassLoader(), SharedMemory.class);
+        Bundle smartActionsBundle = parcel.readBundle(getClass().getClassLoader());
+
+        // Assert the file descriptor is valid
+        assertNotNull(fd);
+        assertFalse(fd.getFd() == -1);
+
+        // Assert that the smart action is in the parcel
+        assertNotNull(smartActionsBundle);
+        ArrayList<Notification.Action> recoveredActions =
+                smartActionsBundle.getParcelableArrayList(TEST_KEY, Notification.Action.class);
+        assertNotNull(recoveredActions);
+        assertEquals(actions.size(), recoveredActions.size());
+        assertEquals(actions.get(0).title.toString(), recoveredActions.get(0).title.toString());
+        parcel.recycle();
+    }
+
+    @Test
+    public void testRankingUpdate_handlesEmptySmartActionList() {
+        if (!mRankingUpdateAshmem) {
+            return;
+        }
+        ArrayList<Notification.Action> actions = new ArrayList<>();
+        NotificationListenerService.Ranking ranking =
+                createEmptyTestRanking(TEST_KEY, 123, actions);
+        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking});
+
+        Parcel parcel = Parcel.obtain();
+        rankingUpdate.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        // Ensure that despite an empty actions list, we can still unparcel the update.
+        NotificationRankingUpdate newRankingUpdate = new NotificationRankingUpdate(parcel);
+        assertNotNull(newRankingUpdate);
+        assertNotNull(newRankingUpdate.getRankingMap());
+        detailedAssertEquals(rankingUpdate, newRankingUpdate);
+        parcel.recycle();
+    }
+
+    @Test
+    public void testRankingUpdate_handlesNullSmartActionList() {
+        if (!mRankingUpdateAshmem) {
+            return;
+        }
+        NotificationListenerService.Ranking ranking =
+                createEmptyTestRanking(TEST_KEY, 123, null);
+        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking});
+
+        Parcel parcel = Parcel.obtain();
+        rankingUpdate.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        // Ensure that despite an empty actions list, we can still unparcel the update.
+        NotificationRankingUpdate newRankingUpdate = new NotificationRankingUpdate(parcel);
+        assertNotNull(newRankingUpdate);
+        assertNotNull(newRankingUpdate.getRankingMap());
+        detailedAssertEquals(rankingUpdate, newRankingUpdate);
+        parcel.recycle();
     }
 }
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/widget/DifferentialMotionFlingHelperTest.java b/core/tests/coretests/src/android/widget/DifferentialMotionFlingHelperTest.java
index 51c8bc0..cce2faf 100644
--- a/core/tests/coretests/src/android/widget/DifferentialMotionFlingHelperTest.java
+++ b/core/tests/coretests/src/android/widget/DifferentialMotionFlingHelperTest.java
@@ -20,6 +20,8 @@
 import static org.junit.Assert.assertFalse;
 
 import android.view.MotionEvent;
+import android.widget.flags.FakeFeatureFlagsImpl;
+import android.widget.flags.Flags;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -54,6 +56,8 @@
     private final TestDifferentialMotionFlingTarget mFlingTarget =
             new TestDifferentialMotionFlingTarget();
 
+    private final FakeFeatureFlagsImpl mFakeWidgetFeatureFlags = new FakeFeatureFlagsImpl();
+
     private DifferentialMotionFlingHelper mFlingHelper;
 
     @Before
@@ -62,7 +66,10 @@
                 ApplicationProvider.getApplicationContext(),
                 mFlingTarget,
                 mVelocityThresholdCalculator,
-                mVelocityProvider);
+                mVelocityProvider,
+                mFakeWidgetFeatureFlags);
+        mFakeWidgetFeatureFlags.setFlag(
+                Flags.FLAG_ENABLE_PLATFORM_WIDGET_DIFFERENTIAL_MOTION_FLING, true);
     }
 
     @Test
@@ -139,6 +146,18 @@
     }
 
     @Test
+    public void flingFeatureFlagDisabled_noFlingCalculation() {
+        mFakeWidgetFeatureFlags.setFlag(
+                Flags.FLAG_ENABLE_PLATFORM_WIDGET_DIFFERENTIAL_MOTION_FLING, false);
+        mMinVelocity = 50;
+        mMaxVelocity = 100;
+        deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, 60);
+
+        assertFalse(mVelocityCalculated);
+        assertEquals(0, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
+    }
+
+    @Test
     public void negativeFlingVelocityAboveMaximum_velocityClamped() {
         mMinVelocity = 50;
         mMaxVelocity = 100;
diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
index 25f5819..6229530 100644
--- a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
+++ b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
@@ -18,6 +18,8 @@
 
 import static android.os.PerformanceHintManager.Session.CPU_LOAD_RESET;
 import static android.os.PerformanceHintManager.Session.CPU_LOAD_UP;
+import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
 import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN;
 import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF;
 import static android.window.SystemPerformanceHinter.HINT_ADPF;
@@ -29,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;
@@ -150,7 +153,11 @@
         verify(mTransaction).setFrameRateSelectionStrategy(
                 eq(mDefaultDisplayRoot),
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
-        verify(mTransaction).apply();
+        verify(mTransaction).setFrameRateCategory(
+                eq(mDefaultDisplayRoot),
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
+        verify(mTransaction).applyAsyncUnsafe();
     }
 
     @Test
@@ -164,7 +171,11 @@
         verify(mTransaction).setFrameRateSelectionStrategy(
                 eq(mDefaultDisplayRoot),
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
-        verify(mTransaction).apply();
+        verify(mTransaction).setFrameRateCategory(
+                eq(mDefaultDisplayRoot),
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
+        verify(mTransaction).applyAsyncUnsafe();
     }
 
     @Test
@@ -177,7 +188,7 @@
 
         // Verify we call SF
         verify(mTransaction).setEarlyWakeupStart();
-        verify(mTransaction).apply();
+        verify(mTransaction).applyAsyncUnsafe();
     }
 
     @Test
@@ -189,7 +200,7 @@
 
         // Verify we call SF
         verify(mTransaction).setEarlyWakeupEnd();
-        verify(mTransaction).apply();
+        verify(mTransaction).applyAsyncUnsafe();
     }
 
     @Test
@@ -231,8 +242,12 @@
         verify(mTransaction).setFrameRateSelectionStrategy(
                 eq(mDefaultDisplayRoot),
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
+        verify(mTransaction).setFrameRateCategory(
+                eq(mDefaultDisplayRoot),
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).setEarlyWakeupStart();
-        verify(mTransaction).apply();
+        verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
     }
 
@@ -248,8 +263,12 @@
         verify(mTransaction).setFrameRateSelectionStrategy(
                 eq(mDefaultDisplayRoot),
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
+        verify(mTransaction).setFrameRateCategory(
+                eq(mDefaultDisplayRoot),
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).setEarlyWakeupEnd();
-        verify(mTransaction).apply();
+        verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
     }
 
@@ -265,8 +284,12 @@
             verify(mTransaction).setFrameRateSelectionStrategy(
                     eq(mDefaultDisplayRoot),
                     eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
+            verify(mTransaction).setFrameRateCategory(
+                    eq(mDefaultDisplayRoot),
+                    eq(FRAME_RATE_CATEGORY_DEFAULT),
+                    eq(false));
             verify(mTransaction).setEarlyWakeupEnd();
-            verify(mTransaction).apply();
+            verify(mTransaction).applyAsyncUnsafe();
             verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
         }
     }
@@ -280,8 +303,12 @@
         verify(mTransaction).setFrameRateSelectionStrategy(
                 eq(mDefaultDisplayRoot),
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
+        verify(mTransaction).setFrameRateCategory(
+                eq(mDefaultDisplayRoot),
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).setEarlyWakeupStart();
-        verify(mTransaction).apply();
+        verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
         reset(mTransaction);
         reset(mAdpfSession);
@@ -290,15 +317,17 @@
                 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(), anyBoolean());
         verify(mTransaction, never()).setEarlyWakeupEnd();
-        verify(mTransaction, never()).apply();
+        verify(mTransaction, never()).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
 
         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(), anyBoolean());
         verify(mTransaction, never()).setEarlyWakeupEnd();
-        verify(mTransaction, never()).apply();
+        verify(mTransaction, never()).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
 
         session1.close();
@@ -306,8 +335,12 @@
         verify(mTransaction).setFrameRateSelectionStrategy(
                 eq(mDefaultDisplayRoot),
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
+        verify(mTransaction).setFrameRateCategory(
+                eq(mDefaultDisplayRoot),
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).setEarlyWakeupEnd();
-        verify(mTransaction).apply();
+        verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
     }
 
@@ -321,8 +354,12 @@
         verify(mTransaction).setFrameRateSelectionStrategy(
                 eq(mDefaultDisplayRoot),
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
+        verify(mTransaction).setFrameRateCategory(
+                eq(mDefaultDisplayRoot),
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).setEarlyWakeupStart();
-        verify(mTransaction).apply();
+        verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
         reset(mTransaction);
         reset(mAdpfSession);
@@ -333,8 +370,12 @@
         verify(mTransaction).setFrameRateSelectionStrategy(
                 eq(mSecondaryDisplayRoot),
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
+        verify(mTransaction).setFrameRateCategory(
+                eq(mSecondaryDisplayRoot),
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction, never()).setEarlyWakeupStart();
-        verify(mTransaction).apply();
+        verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
         reset(mTransaction);
         reset(mAdpfSession);
@@ -345,11 +386,19 @@
         verify(mTransaction).setFrameRateSelectionStrategy(
                 eq(mDefaultDisplayRoot),
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
+        verify(mTransaction).setFrameRateCategory(
+                eq(mDefaultDisplayRoot),
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction, never()).setFrameRateSelectionStrategy(
                 eq(mSecondaryDisplayRoot),
                 anyInt());
+        verify(mTransaction, never()).setFrameRateCategory(
+                eq(mSecondaryDisplayRoot),
+                anyInt(),
+                eq(false));
         verify(mTransaction, never()).setEarlyWakeupEnd();
-        verify(mTransaction).apply();
+        verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
         reset(mTransaction);
         reset(mAdpfSession);
@@ -362,8 +411,12 @@
         verify(mTransaction).setFrameRateSelectionStrategy(
                 eq(mSecondaryDisplayRoot),
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
+        verify(mTransaction).setFrameRateCategory(
+                eq(mSecondaryDisplayRoot),
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).setEarlyWakeupEnd();
-        verify(mTransaction).apply();
+        verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
index 681ba9c..06b62f8 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -29,6 +29,8 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.hamcrest.Matchers.allOf;
 import static org.hamcrest.Matchers.endsWith;
 import static org.mockito.ArgumentMatchers.any;
@@ -39,6 +41,8 @@
 import static org.mockito.Mockito.when;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.AlertDialog;
+import android.app.KeyguardManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -47,11 +51,17 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.os.Handler;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject2;
 import android.support.test.uiautomator.Until;
+import android.view.View;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
 import android.view.accessibility.IAccessibilityManager;
 
 import androidx.lifecycle.Lifecycle;
@@ -90,6 +100,9 @@
     private TestAccessibilityShortcutChooserActivity mActivity;
 
     @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Mock
     private AccessibilityServiceInfo mAccessibilityServiceInfo;
@@ -101,6 +114,8 @@
     private ApplicationInfo mApplicationInfo;
     @Mock
     private IAccessibilityManager mAccessibilityManagerService;
+    @Mock
+    private KeyguardManager mKeyguardManager;
 
     @Before
     public void setUp() throws Exception {
@@ -116,22 +131,19 @@
                         Collections.singletonList(mAccessibilityServiceInfo)));
         when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
                 anyString(), anyInt(), anyInt())).thenReturn(true);
-        TestAccessibilityShortcutChooserActivity.setupForTesting(mAccessibilityManagerService);
-        mScenario = ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
-        mScenario.onActivity(activity -> mActivity = activity);
-        mScenario.moveToState(Lifecycle.State.CREATED);
-        mScenario.moveToState(Lifecycle.State.STARTED);
-        mScenario.moveToState(Lifecycle.State.RESUMED);
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
+        TestAccessibilityShortcutChooserActivity.setupForTesting(
+                mAccessibilityManagerService, mKeyguardManager);
     }
 
     @After
     public void cleanUp() {
-        mScenario.moveToState(Lifecycle.State.DESTROYED);
+        mScenario.close();
     }
 
     @Test
     public void doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist() {
+        launchActivity();
         openShortcutsList();
 
         // Performing the double-click is flaky so retry if needed.
@@ -154,6 +166,7 @@
             throws Exception {
         when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
                 eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt())).thenReturn(false);
+        launchActivity();
         openShortcutsList();
 
         onView(withText(TEST_LABEL)).perform(scrollTo(), click());
@@ -165,6 +178,7 @@
     @Test
     public void popEditShortcutMenuList_oneHandedModeEnabled_shouldBeInListView() {
         TestUtils.setOneHandedModeEnabled(this, /* enabled= */ true);
+        launchActivity();
         openShortcutsList();
 
         onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp());
@@ -176,6 +190,7 @@
     @Test
     public void popEditShortcutMenuList_oneHandedModeDisabled_shouldNotBeInListView() {
         TestUtils.setOneHandedModeEnabled(this, /* enabled= */ false);
+        launchActivity();
         openShortcutsList();
 
         onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp());
@@ -184,6 +199,30 @@
         onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(doesNotExist());
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ALLOW_SHORTCUT_CHOOSER_ON_LOCKSCREEN)
+    public void createDialog_onLockscreen_hasExpectedContent() {
+        when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+        launchActivity();
+
+        final AlertDialog dialog = mActivity.getMenuDialog();
+
+        assertThat(dialog.getButton(AlertDialog.BUTTON_POSITIVE).getVisibility())
+                .isEqualTo(View.GONE);
+        assertThat(dialog.getWindow().getAttributes().flags
+                & WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
+                .isEqualTo(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+    }
+
+    private void launchActivity() {
+        mScenario = ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
+        mScenario.onActivity(activity -> mActivity = activity);
+        mScenario.moveToState(Lifecycle.State.CREATED);
+        mScenario.moveToState(Lifecycle.State.STARTED);
+        mScenario.moveToState(Lifecycle.State.RESUMED);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
     private void openShortcutsList() {
         UiObject2 editButton = mDevice.findObject(By.text(EDIT_LABEL));
         if (editButton != null) {
@@ -198,9 +237,13 @@
     public static class TestAccessibilityShortcutChooserActivity extends
             AccessibilityShortcutChooserActivity {
         private static IAccessibilityManager sAccessibilityManagerService;
+        private static KeyguardManager sKeyguardManager;
 
-        public static void setupForTesting(IAccessibilityManager accessibilityManagerService) {
+        public static void setupForTesting(
+                IAccessibilityManager accessibilityManagerService,
+                KeyguardManager keyguardManager) {
             sAccessibilityManagerService = accessibilityManagerService;
+            sKeyguardManager = keyguardManager;
         }
 
         @Override
@@ -210,6 +253,9 @@
                 return new AccessibilityManager(this, new Handler(getMainLooper()),
                         sAccessibilityManagerService, /* userId= */ 0, /* serviceConnect= */ true);
             }
+            if (Context.KEYGUARD_SERVICE.equals(name)) {
+                return sKeyguardManager;
+            }
 
             return super.getSystemService(name);
         }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 2cbeaa1..f846ac5 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -34,6 +34,7 @@
         KernelSingleUidTimeReaderTest.class,
         LongArrayMultiStateCounterTest.class,
         LongMultiStateCounterTest.class,
+        MonotonicClockTest.class,
         PowerProfileTest.class,
         PowerStatsTest.class,
 
diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
new file mode 100644
index 0000000..7951270
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
@@ -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.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.util.Xml;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MonotonicClockTest {
+    private final MockClock mClock = new MockClock();
+
+    @Test
+    public void persistence() throws IOException {
+        MonotonicClock monotonicClock = new MonotonicClock(1000, mClock);
+        mClock.realtime = 234;
+
+        assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
+
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        monotonicClock.writeXml(out, Xml.newFastSerializer());
+        String xml = out.toString();
+        assertThat(xml).contains("timeshift=\"1234\"");
+
+        mClock.realtime = 42;
+        MonotonicClock newMonotonicClock = new MonotonicClock(0, mClock);
+        newMonotonicClock.readXml(new ByteArrayInputStream(out.toByteArray()),
+                Xml.newFastPullParser());
+
+        mClock.realtime = 2000;
+        assertThat(newMonotonicClock.monotonicTime()).isEqualTo(1234 - 42 + 2000);
+    }
+}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 9c65287..b71aaf3 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1801,6 +1801,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-504637678": {
+      "message": "Starting animation on dim layer %s, requested by %s, alpha: %f -> %f, blur: %d -> %d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_DIMMER",
+      "at": "com\/android\/server\/wm\/SmoothDimmer.java"
+    },
     "-503656156": {
       "message": "Update process config of %s to new config %s",
       "level": "VERBOSE",
@@ -3739,6 +3745,12 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityClientController.java"
     },
+    "1309365288": {
+      "message": "Removing dim surface %s on transaction %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_DIMMER",
+      "at": "com\/android\/server\/wm\/SmoothDimmer.java"
+    },
     "1316533291": {
       "message": "State movement: %s from:%s to:%s reason:%s",
       "level": "VERBOSE",
@@ -4003,6 +4015,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1620751818": {
+      "message": "Dim %s skipping animation and directly setting alpha=%f, blur=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_DIMMER",
+      "at": "com\/android\/server\/wm\/SmoothDimmer.java"
+    },
     "1621562070": {
       "message": "    startWCT=%s",
       "level": "VERBOSE",
@@ -4560,6 +4578,9 @@
     "WM_DEBUG_CONTENT_RECORDING": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_DIMMER": {
+      "tag": "WindowManager"
+    },
     "WM_DEBUG_DRAW": {
       "tag": "WindowManager"
     },
diff --git a/framework-minus-apex-ravenwood-policies.txt b/framework-minus-apex-ravenwood-policies.txt
new file mode 100644
index 0000000..6bac58b
--- /dev/null
+++ b/framework-minus-apex-ravenwood-policies.txt
@@ -0,0 +1 @@
+# Ravenwood "policy" file for framework-minus-apex.
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/Canvas.java b/graphics/java/android/graphics/Canvas.java
index e7814cb..d1aceaf 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -114,6 +114,7 @@
             throw new IllegalStateException("Immutable bitmap passed to Canvas constructor");
         }
         throwIfCannotDraw(bitmap);
+        bitmap.setGainmap(null);
         mNativeCanvasWrapper = nInitRaster(bitmap.getNativeInstance());
         mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
                 this, mNativeCanvasWrapper);
@@ -178,7 +179,7 @@
                 throw new IllegalStateException();
             }
             throwIfCannotDraw(bitmap);
-
+            bitmap.setGainmap(null);
             nSetBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance());
             mDensity = bitmap.mDensity;
         }
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 735bc18..52b0b95 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -236,7 +236,9 @@
             }
         }
 
-        return new FontConfig(families, filtered, resultNamedFamilies, lastModifiedDate,
+        return new FontConfig(families, filtered, resultNamedFamilies,
+                customization.getLocaleFamilyCustomizations(),
+                lastModifiedDate,
                 configVersion);
     }
 
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/Paint.java b/graphics/java/android/graphics/Paint.java
index 9d32272..9fde0fd 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -16,8 +16,11 @@
 
 package android.graphics;
 
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
+
 import android.annotation.ColorInt;
 import android.annotation.ColorLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -1606,7 +1609,7 @@
     /**
      * Returns the color of the shadow layer.
      *
-     * @return the shadow layer's color encoded as a {@link ColorLong}.
+     * @return the shadow layer's color encoded as a {@code ColorLong}.
      * @see #setShadowLayer(float,float,float,int)
      * @see #setShadowLayer(float,float,float,long)
      * @see Color
@@ -2125,7 +2128,7 @@
      * @return the font's recommended interline spacing.
      */
     public float getFontMetrics(FontMetrics metrics) {
-        return nGetFontMetrics(mNativePaint, metrics);
+        return nGetFontMetrics(mNativePaint, metrics, false);
     }
 
     /**
@@ -2139,6 +2142,32 @@
     }
 
     /**
+     * Get the font metrics used for the locale
+     *
+     * Obtain the metrics of the font that is used for the specified locale by
+     * {@link #setTextLocales(LocaleList)}. If multiple locales are specified, the minimum ascent
+     * and maximum descent will be set.
+     *
+     * This API is useful for determining the default line height of the empty text field, e.g.
+     * {@link android.widget.EditText}.
+     *
+     * Note, if the {@link android.graphics.Typeface} is created from the custom font files, its
+     * metrics are reserved, i.e. the ascent will be the custom font's ascent or smaller, the
+     * descent will be the custom font's descent or larger.
+     *
+     * Note, if the {@link android.graphics.Typeface} is a system fallback, e.g.
+     * {@link android.graphics.Typeface#SERIF}, the default font's metrics are reserved, i.e. the
+     * ascent will be the serif font's ascent or smaller, the descent will be the serif font's
+     * descent or larger.
+     *
+     * @param metrics an output parameter. All members will be set by calling this function.
+     */
+    @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+    public void getFontMetricsForLocale(@NonNull FontMetrics metrics) {
+        nGetFontMetrics(mNativePaint, metrics, true);
+    }
+
+    /**
      * Returns the font metrics value for the given text.
      *
      * If the text is rendered with multiple font files, this function returns the large ascent and
@@ -2318,7 +2347,7 @@
      * @return the font's interline spacing.
      */
     public int getFontMetricsInt(FontMetricsInt fmi) {
-        return nGetFontMetricsInt(mNativePaint, fmi);
+        return nGetFontMetricsInt(mNativePaint, fmi, false);
     }
 
     public FontMetricsInt getFontMetricsInt() {
@@ -2328,6 +2357,32 @@
     }
 
     /**
+     * Get the font metrics used for the locale
+     *
+     * Obtain the metrics of the font that is used for the specified locale by
+     * {@link #setTextLocales(LocaleList)}. If multiple locales are specified, the minimum ascent
+     * and maximum descent will be set.
+     *
+     * This API is useful for determining the default line height of the empty text field, e.g.
+     * {@link android.widget.EditText}.
+     *
+     * Note, if the {@link android.graphics.Typeface} is created from the custom font files, its
+     * metrics are reserved, i.e. the ascent will be the custom font's ascent or smaller, the
+     * descent will be the custom font's descent or larger.
+     *
+     * Note, if the {@link android.graphics.Typeface} is a system fallback, e.g.
+     * {@link android.graphics.Typeface#SERIF}, the default font's metrics are reserved, i.e. the
+     * ascent will be the serif font's ascent or smaller, the descent will be the serif font's
+     * descent or larger.
+     *
+     * @param metrics an output parameter. All members will be set by calling this function.
+     */
+    @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+    public void getFontMetricsIntForLocale(@NonNull FontMetricsInt metrics) {
+        nGetFontMetricsInt(mNativePaint, metrics, true);
+    }
+
+    /**
      * Return the recommend line spacing based on the current typeface and
      * text size.
      *
@@ -3446,9 +3501,11 @@
     @FastNative
     private static native void nSetFontFeatureSettings(long paintPtr, String settings);
     @FastNative
-    private static native float nGetFontMetrics(long paintPtr, FontMetrics metrics);
+    private static native float nGetFontMetrics(long paintPtr, FontMetrics metrics,
+            boolean useLocale);
     @FastNative
-    private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi);
+    private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi,
+            boolean useLocale);
 
     // ---------------- @CriticalNative ------------------------
 
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/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
index b458dd9..6e04a2f 100644
--- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java
+++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.FontListParser;
+import android.text.FontConfig;
 import android.util.Xml;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -34,6 +35,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 /**
@@ -52,14 +54,19 @@
 
         private final List<Alias> mAdditionalAliases;
 
+        private final List<FontConfig.Customization.LocaleFallback> mLocaleFamilyCustomizations;
+
         public Result() {
             mAdditionalNamedFamilies = Collections.emptyMap();
+            mLocaleFamilyCustomizations = Collections.emptyList();
             mAdditionalAliases = Collections.emptyList();
         }
 
         public Result(Map<String, NamedFamilyList> additionalNamedFamilies,
+                List<FontConfig.Customization.LocaleFallback> localeFamilyCustomizations,
                 List<Alias> additionalAliases) {
             mAdditionalNamedFamilies = additionalNamedFamilies;
+            mLocaleFamilyCustomizations = localeFamilyCustomizations;
             mAdditionalAliases = additionalAliases;
         }
 
@@ -70,6 +77,10 @@
         public List<Alias> getAdditionalAliases() {
             return mAdditionalAliases;
         }
+
+        public List<FontConfig.Customization.LocaleFallback> getLocaleFamilyCustomizations() {
+            return mLocaleFamilyCustomizations;
+        }
     }
 
     /**
@@ -89,7 +100,9 @@
     }
 
     private static Result validateAndTransformToResult(
-            List<NamedFamilyList> families, List<Alias> aliases) {
+            List<NamedFamilyList> families,
+            List<FontConfig.Customization.LocaleFallback> outLocaleFamilies,
+            List<Alias> aliases) {
         HashMap<String, NamedFamilyList> namedFamily = new HashMap<>();
         for (int i = 0; i < families.size(); ++i) {
             final NamedFamilyList family = families.get(i);
@@ -105,7 +118,7 @@
                                 + "requires fallackTarget attribute");
             }
         }
-        return new Result(namedFamily, aliases);
+        return new Result(namedFamily, outLocaleFamilies, aliases);
     }
 
     private static Result readFamilies(
@@ -115,12 +128,13 @@
     ) throws XmlPullParserException, IOException {
         List<NamedFamilyList> families = new ArrayList<>();
         List<Alias> aliases = new ArrayList<>();
+        List<FontConfig.Customization.LocaleFallback> outLocaleFamilies = new ArrayList<>();
         parser.require(XmlPullParser.START_TAG, null, "fonts-modification");
         while (parser.next() != XmlPullParser.END_TAG) {
             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
             String tag = parser.getName();
             if (tag.equals("family")) {
-                readFamily(parser, fontDir, families, updatableFontMap);
+                readFamily(parser, fontDir, families, outLocaleFamilies, updatableFontMap);
             } else if (tag.equals("family-list")) {
                 readFamilyList(parser, fontDir, families, updatableFontMap);
             } else if (tag.equals("alias")) {
@@ -129,13 +143,14 @@
                 FontListParser.skip(parser);
             }
         }
-        return validateAndTransformToResult(families, aliases);
+        return validateAndTransformToResult(families, outLocaleFamilies, aliases);
     }
 
     private static void readFamily(
             @NonNull XmlPullParser parser,
             @NonNull String fontDir,
             @NonNull List<NamedFamilyList> out,
+            @NonNull List<FontConfig.Customization.LocaleFallback> outCustomization,
             @Nullable Map<String, File> updatableFontMap)
             throws XmlPullParserException, IOException {
         final String customizationType = parser.getAttributeValue(null, "customizationType");
@@ -148,6 +163,29 @@
             if (fontFamily != null) {
                 out.add(fontFamily);
             }
+        } else if (customizationType.equals("new-locale-family")) {
+            final String lang = parser.getAttributeValue(null, "lang");
+            final String op = parser.getAttributeValue(null, "operation");
+            final int intOp;
+            if (op.equals("append")) {
+                intOp = FontConfig.Customization.LocaleFallback.OPERATION_APPEND;
+            } else if (op.equals("prepend")) {
+                intOp = FontConfig.Customization.LocaleFallback.OPERATION_PREPEND;
+            } else if (op.equals("replace")) {
+                intOp = FontConfig.Customization.LocaleFallback.OPERATION_REPLACE;
+            } else {
+                throw new IllegalArgumentException("Unknown operation=" + op);
+            }
+
+            final FontConfig.FontFamily family = FontListParser.readFamily(
+                    parser, fontDir, updatableFontMap, false);
+
+            // For ignoring the customization, consume the new-locale-family element but don't
+            // register any customizations.
+            if (com.android.text.flags.Flags.customLocaleFallback()) {
+                outCustomization.add(new FontConfig.Customization.LocaleFallback(
+                        Locale.forLanguageTag(lang), intOp, family));
+            }
         } else {
             throw new IllegalArgumentException("Unknown customizationType=" + customizationType);
         }
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index d4e35b3..618aa5b 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -16,10 +16,15 @@
 
 package android.graphics.fonts;
 
+import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_APPEND;
+import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_PREPEND;
+import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_REPLACE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.FontListParser;
 import android.graphics.Typeface;
+import android.os.LocaleList;
 import android.text.FontConfig;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -38,6 +43,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
@@ -119,7 +125,6 @@
             }
         }
 
-
         final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
                 defaultFonts, languageTags, variant, xmlFamily.getVariableFontFamilyType(), false,
                 cache);
@@ -300,11 +305,11 @@
         } catch (IOException e) {
             Log.e(TAG, "Failed to open/read system font configurations.", e);
             return new FontConfig(Collections.emptyList(), Collections.emptyList(),
-                    Collections.emptyList(), 0, 0);
+                    Collections.emptyList(), Collections.emptyList(), 0, 0);
         } catch (XmlPullParserException e) {
             Log.e(TAG, "Failed to parse the system font configuration.", e);
             return new FontConfig(Collections.emptyList(), Collections.emptyList(),
-                    Collections.emptyList(), 0, 0);
+                    Collections.emptyList(), Collections.emptyList(), 0, 0);
         }
     }
 
@@ -328,6 +333,8 @@
             ArrayMap<String, ByteBuffer> outBufferCache) {
 
         final ArrayMap<String, NativeFamilyListSet> fallbackListMap = new ArrayMap<>();
+        final List<FontConfig.Customization.LocaleFallback> localeFallbacks =
+                fontConfig.getLocaleFallbackCustomizations();
 
         final List<FontConfig.NamedFamilyList> namedFamilies = fontConfig.getNamedFamilyLists();
         for (int i = 0; i < namedFamilies.size(); ++i) {
@@ -336,10 +343,54 @@
         }
 
         // Then, add fallback fonts to the fallback map.
+        final List<FontConfig.Customization.LocaleFallback> customizations = new ArrayList<>();
         final List<FontConfig.FontFamily> xmlFamilies = fontConfig.getFontFamilies();
+        final SparseIntArray seenCustomization = new SparseIntArray();
         for (int i = 0; i < xmlFamilies.size(); i++) {
             final FontConfig.FontFamily xmlFamily = xmlFamilies.get(i);
-            pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache);
+
+            customizations.clear();
+            for (int j = 0; j < localeFallbacks.size(); ++j) {
+                if (seenCustomization.get(j, -1) != -1) {
+                    continue;  // The customization is already applied.
+                }
+                FontConfig.Customization.LocaleFallback localeFallback = localeFallbacks.get(j);
+                if (scriptMatch(xmlFamily.getLocaleList(), localeFallback.getScript())) {
+                    customizations.add(localeFallback);
+                    seenCustomization.put(j, 1);
+                }
+            }
+
+            if (customizations.isEmpty()) {
+                pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache);
+            } else {
+                for (int j = 0; j < customizations.size(); ++j) {
+                    FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j);
+                    if (localeFallback.getOperation() == OPERATION_PREPEND) {
+                        pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap,
+                                outBufferCache);
+                    }
+                }
+                boolean isReplaced = false;
+                for (int j = 0; j < customizations.size(); ++j) {
+                    FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j);
+                    if (localeFallback.getOperation() == OPERATION_REPLACE) {
+                        pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap,
+                                outBufferCache);
+                        isReplaced = true;
+                    }
+                }
+                if (!isReplaced) {  // If nothing is replaced, push the original one.
+                    pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache);
+                }
+                for (int j = 0; j < customizations.size(); ++j) {
+                    FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j);
+                    if (localeFallback.getOperation() == OPERATION_APPEND) {
+                        pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap,
+                                outBufferCache);
+                    }
+                }
+            }
         }
 
         // Build the font map and fallback map.
@@ -365,4 +416,42 @@
         Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result);
         return result;
     }
+
+    private static boolean scriptMatch(LocaleList localeList, String targetScript) {
+        if (localeList == null || localeList.isEmpty()) {
+            return false;
+        }
+        for (int i = 0; i < localeList.size(); ++i) {
+            Locale locale = localeList.get(i);
+            if (locale == null) {
+                continue;
+            }
+            String baseScript = FontConfig.resolveScript(locale);
+            if (baseScript.equals(targetScript)) {
+                return true;
+            }
+
+            // Subtag match
+            if (targetScript.equals("Bopo") && baseScript.equals("Hanb")) {
+                // Hanb is Han with Bopomofo.
+                return true;
+            } else if (targetScript.equals("Hani")) {
+                if (baseScript.equals("Hanb") || baseScript.equals("Hans")
+                        || baseScript.equals("Hant") || baseScript.equals("Kore")
+                        || baseScript.equals("Jpan")) {
+                    // Han id suppoted by Taiwanese, Traditional Chinese, Simplified Chinese, Korean
+                    // and Japanese.
+                    return true;
+                }
+            } else if (targetScript.equals("Hira") || targetScript.equals("Hrkt")
+                    || targetScript.equals("Kana")) {
+                if (baseScript.equals("Jpan") || baseScript.equals("Hrkt")) {
+                    // Hiragana, Hiragana-Katakana, Katakana is supported by Japanese and
+                    // Hiragana-Katakana script.
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
 }
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index e81525f..f5e5803 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -134,10 +134,25 @@
      */
     public static final int LINE_BREAK_STYLE_STRICT = 3;
 
+    /**
+     * The line break style that used for preventing automatic line breaking.
+     *
+     * This is useful when you want to preserve some words in the same line by using
+     * {@link android.text.style.LineBreakConfigSpan} or
+     * {@link android.text.style.LineBreakConfigSpan.NoBreakSpan} as a shorthand.
+     * Note that even if this style is specified, the grapheme based line break is still performed
+     * for preventing clipping text.
+     *
+     * @see android.text.style.LineBreakConfigSpan
+     * @see android.text.style.LineBreakConfigSpan.NoBreakSpan
+     */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public static final int LINE_BREAK_STYLE_NO_BREAK = 4;
+
     /** @hide */
     @IntDef(prefix = { "LINE_BREAK_STYLE_" }, value = {
             LINE_BREAK_STYLE_NONE, LINE_BREAK_STYLE_LOOSE, LINE_BREAK_STYLE_NORMAL,
-            LINE_BREAK_STYLE_STRICT, LINE_BREAK_STYLE_UNSPECIFIED
+            LINE_BREAK_STYLE_STRICT, LINE_BREAK_STYLE_UNSPECIFIED, LINE_BREAK_STYLE_NO_BREAK
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface LineBreakStyle {}
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index dc2e794..0e3fb16 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -249,7 +249,7 @@
          * @param useBoundsForWidth True for using bounding box, false for advances.
          * @return this builder instance
          * @see Layout#getUseBoundsForWidth()
-         * @see StaticLayout.Builder#setUseBoundsForWidth(boolean)
+         * @see android.text.StaticLayout.Builder#setUseBoundsForWidth(boolean)
          */
         @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
         public @NonNull Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 0e198d5..e6de597 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -322,7 +322,7 @@
         }
 
         /**
-         * Returns the {@link CopyResultStatus} of the copy request.
+         * Returns the status of the copy request.
          */
         public @CopyResultStatus int getStatus() {
             return mStatus;
diff --git a/keystore/aaid/aidl/Android.bp b/keystore/aaid/aidl/Android.bp
new file mode 100644
index 0000000..97acfb4
--- /dev/null
+++ b/keystore/aaid/aidl/Android.bp
@@ -0,0 +1,31 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aidl_interface {
+    name: "android.security.aaid_aidl",
+    srcs: ["android/security/keystore/*.aidl"],
+    unstable: true,
+    backend: {
+        rust: {
+            enabled: true,
+        },
+        cpp: {
+            enabled: true,
+        },
+    },
+}
diff --git a/keystore/aaid/aidl/android/security/keystore/IKeyAttestationApplicationIdProvider.aidl b/keystore/aaid/aidl/android/security/keystore/IKeyAttestationApplicationIdProvider.aidl
new file mode 100644
index 0000000..c360cb8
--- /dev/null
+++ b/keystore/aaid/aidl/android/security/keystore/IKeyAttestationApplicationIdProvider.aidl
@@ -0,0 +1,28 @@
+/**
+ * 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.security.keystore;
+
+import android.security.keystore.KeyAttestationApplicationId;
+
+/** @hide */
+interface IKeyAttestationApplicationIdProvider {
+    /**
+     * Provides information describing the possible applications identified by a UID.
+     * @hide
+     */
+    KeyAttestationApplicationId getKeyAttestationApplicationId(int uid);
+}
diff --git a/keystore/aaid/aidl/android/security/keystore/KeyAttestationApplicationId.aidl b/keystore/aaid/aidl/android/security/keystore/KeyAttestationApplicationId.aidl
new file mode 100644
index 0000000..c33e830
--- /dev/null
+++ b/keystore/aaid/aidl/android/security/keystore/KeyAttestationApplicationId.aidl
@@ -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 android.security.keystore;
+
+import android.security.keystore.KeyAttestationPackageInfo;
+
+/**
+ * @hide
+ * The information aggregated by this parcelable is used by keystore to identify a caller of the
+ * keystore API toward a remote party. It aggregates multiple PackageInfos because keystore
+ * can only determine a caller by uid granularity, and a uid can be shared by multiple packages.
+ * The remote party must decide if it trusts all of the packages enough to consider the
+ * confidentiality of the key material in question intact.
+ */
+parcelable KeyAttestationApplicationId {
+    KeyAttestationPackageInfo[] packageInfos;
+}
diff --git a/keystore/aaid/aidl/android/security/keystore/KeyAttestationPackageInfo.aidl b/keystore/aaid/aidl/android/security/keystore/KeyAttestationPackageInfo.aidl
new file mode 100644
index 0000000..5f647d0
--- /dev/null
+++ b/keystore/aaid/aidl/android/security/keystore/KeyAttestationPackageInfo.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore;
+
+import android.security.keystore.Signature;
+
+/**
+ * @hide
+ * This parcelable constitutes and excerpt from the PackageManager's PackageInfo for the purpose of
+ * key attestation. It is part of the KeyAttestationApplicationId, which is used by
+ * keystore to identify the caller of the keystore API towards a remote party.
+ */
+parcelable KeyAttestationPackageInfo {
+    String packageName;
+
+    long versionCode;
+
+    Signature[] signatures;
+}
diff --git a/keystore/aaid/aidl/android/security/keystore/Signature.aidl b/keystore/aaid/aidl/android/security/keystore/Signature.aidl
new file mode 100644
index 0000000..800499a
--- /dev/null
+++ b/keystore/aaid/aidl/android/security/keystore/Signature.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2016, The Android Open 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.security.keystore;
+
+/**
+ * @hide
+ * Represents a signature data read from the package file. Extracted from from the PackageManager's
+ * PackageInfo for the purpose of key attestation. It is part of the KeyAttestationPackageInfo,
+ * which is used by keystore to identify the caller of the keystore API towards a remote party.
+ */
+parcelable Signature {
+    /**
+     * Represents signing certificate data associated with application package, signatures are
+     * expected to be a hex-encoded ASCII string representing valid X509 certificate.
+     */
+    byte[] data;
+}
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index 0f3488b..31c2eb2 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -28,8 +28,8 @@
 import android.util.Log;
 
 /**
- * @hide This is the client side for IKeystoreUserManager AIDL.
- * It shall only be used by the LockSettingsService.
+ * @hide This is the client side for IKeystoreMaintenance AIDL.
+ * It is used mainly by LockSettingsService.
  */
 public class AndroidKeyStoreMaintenance {
     private static final String TAG = "AndroidKeyStoreMaintenance";
@@ -66,7 +66,7 @@
     }
 
     /**
-     * Informs Keystore 2.0 about removing a usergit mer
+     * Informs Keystore 2.0 about removing a user
      *
      * @param userId - Android user id of the user being removed
      * @return 0 if successful or a {@code ResponseCode}
@@ -91,7 +91,7 @@
      *
      * @param userId   - Android user id of the user
      * @param password - a secret derived from the synthetic password provided by the
-     *                 LockSettingService
+     *                 LockSettingsService
      * @return 0 if successful or a {@code ResponseCode}
      * @hide
      */
@@ -110,7 +110,7 @@
     }
 
     /**
-     * Informs Keystore 2.0 that an app was uninstalled and the corresponding namspace is to
+     * Informs Keystore 2.0 that an app was uninstalled and the corresponding namespace is to
      * be cleared.
      */
     public static int clearNamespace(@Domain int domain, long namespace) {
@@ -172,10 +172,10 @@
      *                    namespace.
      *
      * @return * 0 on success
-     *         * KEY_NOT_FOUND if the source did not exists.
+     *         * KEY_NOT_FOUND if the source did not exist.
      *         * PERMISSION_DENIED if any of the required permissions was missing.
      *         * INVALID_ARGUMENT if the destination was occupied or any domain value other than
-     *                   the allowed once were specified.
+     *                   the allowed ones was specified.
      *         * SYSTEM_ERROR if an unexpected error occurred.
      */
     public static int migrateKeyNamespace(KeyDescriptor source, KeyDescriptor destination) {
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
index 253d704..5825fac 100644
--- a/keystore/java/android/security/KeyStoreException.java
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -319,7 +319,7 @@
     /**
      * Returns one of the error codes exported by the class.
      *
-     * @return a public error code, one of the values in {@link PublicErrorCode}.
+     * @return a public error code
      */
     @PublicErrorCode
     public int getNumericErrorCode() {
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/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index d55a41f..7c0d0e3 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -5,4 +5,18 @@
     namespace: "multitasking"
     description: "An Example Flag"
     bug: "300136750"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "enable_app_pairs"
+    namespace: "multitasking"
+    description: "Enables the ability to create and save app pairs to the Home screen"
+    bug: "274835596"
+}
+
+flag {
+    name: "desktop_windowing"
+    namespace: "multitasking"
+    description: "Enables desktop windowing"
+    bug: "304778354"
+}
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 97a9d48..7436400 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -58,6 +58,9 @@
          if a custom action is present before closing it. -->
     <integer name="config_pipForceCloseDelay">1000</integer>
 
+    <!-- Allow PIP to resize via pinch gesture. -->
+    <bool name="config_pipEnablePinchResize">true</bool>
+
     <!-- Animation duration when using long press on recents to dock -->
     <integer name="long_press_dock_anim_duration">250</integer>
 
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 7a309f5..1f6f7ae 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -143,7 +143,9 @@
     <dimen name="bubble_expanded_view_padding">16dp</dimen>
     <!-- Padding for the edge of the expanded view that is closest to the edge of the screen used
          when displaying in landscape on a large screen. -->
-    <dimen name="bubble_expanded_view_largescreen_landscape_padding">128dp</dimen>
+    <dimen name="bubble_expanded_view_largescreen_landscape_padding">102dp</dimen>
+    <!-- The width of the expanded view on large screens. -->
+    <dimen name="bubble_expanded_view_largescreen_width">540dp</dimen>
     <!-- This should be at least the size of bubble_expanded_view_padding; it is used to include
          a slight touch slop around the expanded view. -->
     <dimen name="bubble_expanded_view_slop">8dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
index 34bf9e0..2e5448a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
@@ -18,6 +18,7 @@
 
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
 
+import android.annotation.SuppressLint;
 import android.app.WindowConfiguration;
 import android.util.SparseArray;
 import android.view.SurfaceControl;
@@ -29,6 +30,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.sysui.ShellInit;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -44,9 +46,14 @@
     /** Display area leashes, which is mapped by display IDs. */
     private final SparseArray<SurfaceControl> mLeashes = new SparseArray<>();
 
-    public RootDisplayAreaOrganizer(Executor executor) {
+    public RootDisplayAreaOrganizer(@NonNull Executor executor, @NonNull ShellInit shellInit) {
         super(executor);
-        List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_ROOT);
+        shellInit.addInitCallback(this::onInit, this);
+    }
+
+    @SuppressLint("MissingPermission") // Only called by SysUI.
+    private void onInit() {
+        final List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_ROOT);
         for (int i = infos.size() - 1; i >= 0; --i) {
             onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash());
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index 38550b4..ab61a48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -18,6 +18,7 @@
 
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
 
+import android.annotation.SuppressLint;
 import android.annotation.UiContext;
 import android.app.ResourcesManager;
 import android.content.Context;
@@ -38,6 +39,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.sysui.ShellInit;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -69,10 +71,17 @@
 
     private final Context mContext;
 
-    public RootTaskDisplayAreaOrganizer(Executor executor, Context context) {
+    public RootTaskDisplayAreaOrganizer(@NonNull Executor executor, @NonNull Context context,
+            @NonNull ShellInit shellInit) {
         super(executor);
         mContext = context;
-        List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_DEFAULT_TASK_CONTAINER);
+        shellInit.addInitCallback(this::onInit, this);
+    }
+
+    @SuppressLint("MissingPermission") // Only called by SysUI.
+    private void onInit() {
+        final List<DisplayAreaAppearedInfo> infos =
+                registerOrganizer(FEATURE_DEFAULT_TASK_CONTAINER);
         for (int i = infos.size() - 1; i >= 0; --i) {
             onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash());
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index ea7053d..17e06e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -54,10 +54,6 @@
     public static final float FLYOUT_MAX_WIDTH_PERCENT_LARGE_SCREEN = 0.3f;
     /** The max percent of screen width to use for the flyout on phone. */
     public static final float FLYOUT_MAX_WIDTH_PERCENT = 0.6f;
-    /** The percent of screen width for the expanded view on a large screen. **/
-    private static final float EXPANDED_VIEW_LARGE_SCREEN_LANDSCAPE_WIDTH_PERCENT = 0.48f;
-    /** The percent of screen width for the expanded view on a large screen. **/
-    private static final float EXPANDED_VIEW_LARGE_SCREEN_PORTRAIT_WIDTH_PERCENT = 0.70f;
     /** The percent of screen width for the expanded view on a small tablet. **/
     private static final float EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT = 0.72f;
     /** The percent of screen width for the expanded view when shown in the bubble bar. **/
@@ -95,6 +91,7 @@
     private int mPointerWidth;
     private int mPointerHeight;
     private int mPointerOverlap;
+    private int mManageButtonHeightIncludingMargins;
     private int mManageButtonHeight;
     private int mOverflowHeight;
     private int mMinimumFlyoutWidthLargeScreen;
@@ -176,21 +173,20 @@
             mExpandedViewLargeScreenWidth = (int) (bounds.width()
                     * EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT);
         } else {
-            mExpandedViewLargeScreenWidth = isLandscape()
-                    ? (int) (bounds.width() * EXPANDED_VIEW_LARGE_SCREEN_LANDSCAPE_WIDTH_PERCENT)
-                    : (int) (bounds.width() * EXPANDED_VIEW_LARGE_SCREEN_PORTRAIT_WIDTH_PERCENT);
+            mExpandedViewLargeScreenWidth =
+                    res.getDimensionPixelSize(R.dimen.bubble_expanded_view_largescreen_width);
         }
         if (mIsLargeScreen) {
-            if (isLandscape() && !mIsSmallTablet) {
+            if (mIsSmallTablet) {
+                final int centeredInset = (bounds.width() - mExpandedViewLargeScreenWidth) / 2;
+                mExpandedViewLargeScreenInsetClosestEdge = centeredInset;
+                mExpandedViewLargeScreenInsetFurthestEdge = centeredInset;
+            } else {
                 mExpandedViewLargeScreenInsetClosestEdge = res.getDimensionPixelSize(
                         R.dimen.bubble_expanded_view_largescreen_landscape_padding);
                 mExpandedViewLargeScreenInsetFurthestEdge = bounds.width()
                         - mExpandedViewLargeScreenInsetClosestEdge
                         - mExpandedViewLargeScreenWidth;
-            } else {
-                final int centeredInset = (bounds.width() - mExpandedViewLargeScreenWidth) / 2;
-                mExpandedViewLargeScreenInsetClosestEdge = centeredInset;
-                mExpandedViewLargeScreenInsetFurthestEdge = centeredInset;
             }
         } else {
             mExpandedViewLargeScreenInsetClosestEdge = mExpandedViewPadding;
@@ -202,7 +198,9 @@
         mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
         mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
         mPointerOverlap = res.getDimensionPixelSize(R.dimen.bubble_pointer_overlap);
-        mManageButtonHeight = res.getDimensionPixelSize(R.dimen.bubble_manage_button_total_height);
+        mManageButtonHeightIncludingMargins =
+                res.getDimensionPixelSize(R.dimen.bubble_manage_button_total_height);
+        mManageButtonHeight = res.getDimensionPixelSize(R.dimen.bubble_manage_button_height);
         mExpandedViewMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
         mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height);
         mMinimumFlyoutWidthLargeScreen = res.getDimensionPixelSize(
@@ -420,7 +418,7 @@
         int pointerSize = showBubblesVertically()
                 ? mPointerWidth
                 : (mPointerHeight + mPointerMargin);
-        int bottomPadding = isOverflow ? mExpandedViewPadding : mManageButtonHeight;
+        int bottomPadding = isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins;
         return getAvailableRect().height()
                 - expandedContainerY
                 - paddingTop
@@ -438,6 +436,15 @@
             // overflow in landscape on phone is max
             return MAX_HEIGHT;
         }
+
+        if (mIsLargeScreen && !mIsSmallTablet && !isOverflow) {
+            // the expanded view height on large tablets is calculated based on the shortest screen
+            // size and is the same in both portrait and landscape
+            int maxVerticalInset = Math.max(mInsets.top, mInsets.bottom);
+            int shortestScreenSide = Math.min(mScreenRect.height(), mScreenRect.width());
+            return shortestScreenSide - 2 * maxVerticalInset - mManageButtonHeight;
+        }
+
         float desiredHeight = isOverflow
                 ? mOverflowHeight
                 : ((Bubble) bubble).getDesiredHeight(mContext);
@@ -466,7 +473,8 @@
             return topAlignment;
         }
         // If we're here, we're showing vertically & developer has made height less than maximum.
-        int manageButtonHeight = isOverflow ? mExpandedViewPadding : mManageButtonHeight;
+        int manageButtonHeight =
+                isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins;
         float pointerPosition = getPointerPosition(bubblePosition);
         float bottomIfCentered = pointerPosition + (expandedViewHeight / 2) + manageButtonHeight;
         float topIfCentered = pointerPosition - (expandedViewHeight / 2);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS
index 7af0389..6519eee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS
@@ -1 +1,2 @@
 madym@google.com
+hwwang@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
index 931cf0c..c6c9b35 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
@@ -94,6 +94,10 @@
         mCurrentIcon = icon;
         // Remove old image while waiting for the new one to load.
         mIconImageView.setImageDrawable(null);
+        if (icon.getType() == Icon.TYPE_URI || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
+            // Disallow loading icon from content URI
+            return;
+        }
         icon.loadDrawableAsync(mContext, d -> {
             // The image hasn't been set any other way and the drawable belongs to the most
             // recently set Icon.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index 3b32b6c..d520ff7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -126,12 +126,22 @@
     private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
     private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
 
+    // the size of the current bounds relative to the max size spec
+    private float mBoundsScale;
+
     public PipBoundsState(@NonNull Context context, @NonNull SizeSpecSource sizeSpecSource,
             @NonNull PipDisplayLayoutState pipDisplayLayoutState) {
         mContext = context;
         reloadResources();
         mSizeSpecSource = sizeSpecSource;
         mPipDisplayLayoutState = pipDisplayLayoutState;
+
+        // Update the relative proportion of the bounds compared to max possible size. Max size
+        // spec takes the aspect ratio of the bounds into account, so both width and height
+        // scale by the same factor.
+        addPipExclusionBoundsChangeCallback((bounds) -> {
+            mBoundsScale = Math.min((float) bounds.width() / mMaxSize.x, 1.0f);
+        });
     }
 
     /** Reloads the resources. */
@@ -160,6 +170,15 @@
         return new Rect(mBounds);
     }
 
+    /**
+     * Get the scale of the current bounds relative to the maximum size possible.
+     *
+     * @return 1.0 if {@link PipBoundsState#getBounds()} equals {@link PipBoundsState#getMaxSize()}.
+     */
+    public float getBoundsScale() {
+        return mBoundsScale;
+    }
+
     /** Returns the current movement bounds. */
     @NonNull
     public Rect getMovementBounds() {
@@ -622,6 +641,9 @@
         pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
         pw.println(innerPrefix + "mHasUserMovedPip=" + mHasUserMovedPip);
         pw.println(innerPrefix + "mHasUserResizedPip=" + mHasUserResizedPip);
+        pw.println(innerPrefix + "mMinSize=" + mMinSize);
+        pw.println(innerPrefix + "mMaxSize=" + mMaxSize);
+        pw.println(innerPrefix + "mBoundsScale" + mBoundsScale);
         if (mPipReentryState == null) {
             pw.println(innerPrefix + "mPipReentryState=null");
         } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index e6d3abc..c51af46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -110,13 +110,13 @@
 import com.android.wm.shell.unfold.UnfoldTransitionHandler;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
-import java.util.Optional;
-
 import dagger.BindsOptionalOf;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.Optional;
+
 /**
  * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
  * accessible from components within the WM subcomponent (can be explicitly exposed to the
@@ -658,15 +658,15 @@
     @WMSingleton
     @Provides
     static RootTaskDisplayAreaOrganizer provideRootTaskDisplayAreaOrganizer(
-            @ShellMainThread ShellExecutor mainExecutor, Context context) {
-        return new RootTaskDisplayAreaOrganizer(mainExecutor, context);
+            @ShellMainThread ShellExecutor mainExecutor, Context context, ShellInit shellInit) {
+        return new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit);
     }
 
     @WMSingleton
     @Provides
     static RootDisplayAreaOrganizer provideRootDisplayAreaOrganizer(
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new RootDisplayAreaOrganizer(mainExecutor);
+            @ShellMainThread ShellExecutor mainExecutor, ShellInit shellInit) {
+        return new RootDisplayAreaOrganizer(mainExecutor, shellInit);
     }
 
     @WMSingleton
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/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 1064867..63f20fd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -797,21 +797,15 @@
                     mPipBoundsAlgorithm.getMovementBounds(postChangeBounds),
                     mPipBoundsState.getStashedState());
 
-            // Scale PiP on density dpi change, so it appears to be the same size physically.
-            final boolean densityDpiChanged =
-                    mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
-                    && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
-                            != layout.densityDpi());
-            if (densityDpiChanged) {
-                final float scale = (float) layout.densityDpi()
-                        / mPipDisplayLayoutState.getDisplayLayout().densityDpi();
-                postChangeBounds.set(0, 0,
-                        (int) (postChangeBounds.width() * scale),
-                        (int) (postChangeBounds.height() * scale));
-            }
-
             updateDisplayLayout.run();
 
+            // Resize the PiP bounds to be at the same scale relative to the new size spec. For
+            // example, if PiP was resized to 90% of the maximum size on the previous layout,
+            // make sure it is 90% of the new maximum size spec.
+            postChangeBounds.set(0, 0,
+                    (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()),
+                    (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale()));
+
             // Calculate the PiP bounds in the new orientation based on same fraction along the
             // rotated movement bounds.
             final Rect postChangeMovementBounds = mPipBoundsAlgorithm.getMovementBounds(
@@ -822,6 +816,15 @@
                     mPipDisplayLayoutState.getDisplayBounds(),
                     mPipDisplayLayoutState.getDisplayLayout().stableInsets());
 
+            // make sure we user resize to the updated bounds to avoid animating to any outdated
+            // sizes from the previous layout upon double tap CUJ
+            mPipBoundsState.setHasUserResizedPip(true);
+            mTouchHandler.setUserResizeBounds(postChangeBounds);
+
+            final boolean densityDpiChanged =
+                    mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
+                            && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
+                            != layout.densityDpi());
             if (densityDpiChanged) {
                 // Using PipMotionHelper#movePip directly here may cause race condition since
                 // the app content in PiP mode may or may not be updated for the new density dpi.
@@ -833,15 +836,6 @@
                 // Directly move PiP to its final destination bounds without animation.
                 mPipTaskOrganizer.scheduleFinishResizePip(postChangeBounds);
             }
-
-            // if the pip window size is beyond allowed bounds user resize to normal bounds
-            if (mPipBoundsState.getBounds().width() < mPipBoundsState.getMinSize().x
-                    || mPipBoundsState.getBounds().width() > mPipBoundsState.getMaxSize().x
-                    || mPipBoundsState.getBounds().height() < mPipBoundsState.getMinSize().y
-                    || mPipBoundsState.getBounds().height() > mPipBoundsState.getMaxSize().y) {
-                mTouchHandler.userResizeTo(mPipBoundsState.getNormalBounds(), snapFraction);
-            }
-
         } else {
             updateDisplayLayout.run();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index e5f9fdc..f175775 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -15,7 +15,6 @@
  */
 package com.android.wm.shell.pip.phone;
 
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_PINCH_RESIZE;
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
@@ -31,7 +30,6 @@
 import android.graphics.Region;
 import android.hardware.input.InputManager;
 import android.os.Looper;
-import android.provider.DeviceConfig;
 import android.view.BatchedInputEventReceiver;
 import android.view.Choreographer;
 import android.view.InputChannel;
@@ -155,21 +153,8 @@
         mContext.getDisplay().getRealSize(mMaxSize);
         reloadResources();
 
-        mEnablePinchResize = DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_SYSTEMUI,
-                PIP_PINCH_RESIZE,
-                /* defaultValue = */ true);
-        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
-                mMainExecutor,
-                new DeviceConfig.OnPropertiesChangedListener() {
-                    @Override
-                    public void onPropertiesChanged(DeviceConfig.Properties properties) {
-                        if (properties.getKeyset().contains(PIP_PINCH_RESIZE)) {
-                            mEnablePinchResize = properties.getBoolean(
-                                    PIP_PINCH_RESIZE, /* defaultValue = */ true);
-                        }
-                    }
-                });
+        final Resources res = mContext.getResources();
+        mEnablePinchResize = res.getBoolean(R.bool.config_pipEnablePinchResize);
     }
 
     public void onConfigurationChanged() {
@@ -579,6 +564,12 @@
                     resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
                 }
 
+                // If user resize is smaller than min size, auto resize to min
+                if (mLastResizeBounds.width() < mMinSize.x
+                        || mLastResizeBounds.height() < mMinSize.y) {
+                    resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y);
+                }
+
                 // get the current movement bounds
                 final Rect movementBounds = mPipBoundsAlgorithm
                         .getMovementBounds(mLastResizeBounds);
@@ -679,6 +670,8 @@
         pw.println(innerPrefix + "mEnablePinchResize=" + mEnablePinchResize);
         pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed);
         pw.println(innerPrefix + "mOhmOffset=" + mOhmOffset);
+        pw.println(innerPrefix + "mMinSize=" + mMinSize);
+        pw.println(innerPrefix + "mMaxSize=" + mMaxSize);
     }
 
     class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 2ce4fb9..452a416 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -779,13 +779,10 @@
     }
 
     /**
-     * Resizes the pip window and updates user resized bounds
-     *
-     * @param bounds target bounds to resize to
-     * @param snapFraction snap fraction to apply after resizing
+     * Sets the user resize bounds tracked by {@link PipResizeGestureHandler}
      */
-    void userResizeTo(Rect bounds, float snapFraction) {
-        mPipResizeGestureHandler.userResizeTo(bounds, snapFraction);
+    void setUserResizeBounds(Rect bounds) {
+        mPipResizeGestureHandler.setUserResizeBounds(bounds);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 5e2c61b..68ca231 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -143,6 +143,8 @@
 import com.android.wm.shell.util.TransitionUtil;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -2240,6 +2242,25 @@
         return SPLIT_POSITION_UNDEFINED;
     }
 
+    /**
+     * Returns the {@link StageType} where {@param token} is being used
+     * {@link SplitScreen#STAGE_TYPE_UNDEFINED} otherwise
+     */
+    @StageType
+    public int getSplitItemStage(@Nullable WindowContainerToken token) {
+        if (token == null) {
+            return STAGE_TYPE_UNDEFINED;
+        }
+
+        if (mMainStage.containsToken(token)) {
+            return STAGE_TYPE_MAIN;
+        } else if (mSideStage.containsToken(token)) {
+            return STAGE_TYPE_SIDE;
+        }
+
+        return STAGE_TYPE_UNDEFINED;
+    }
+
     @Override
     public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
         final StageTaskListener topLeftStage =
@@ -2477,7 +2498,16 @@
                 mRecentTasks.ifPresent(
                         recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId));
             }
-            prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, outWCT);
+            @StageType int topStage = STAGE_TYPE_UNDEFINED;
+            if (isSplitScreenVisible()) {
+                // Get the stage where a child exists to keep that stage onTop
+                if (mMainStage.getChildCount() != 0 && mSideStage.getChildCount() == 0) {
+                    topStage = STAGE_TYPE_MAIN;
+                } else if (mSideStage.getChildCount() != 0 && mMainStage.getChildCount() == 0) {
+                    topStage = STAGE_TYPE_SIDE;
+                }
+            }
+            prepareExitSplitScreen(topStage, outWCT);
         }
     }
 
@@ -2691,7 +2721,7 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         boolean shouldAnimate = true;
         if (mSplitTransitions.isPendingEnter(transition)) {
-            shouldAnimate = startPendingEnterAnimation(
+            shouldAnimate = startPendingEnterAnimation(transition,
                     mSplitTransitions.mPendingEnter, info, startTransaction, finishTransaction);
         } else if (mSplitTransitions.isPendingDismiss(transition)) {
             final SplitScreenTransitions.DismissSession dismiss = mSplitTransitions.mPendingDismiss;
@@ -2730,7 +2760,7 @@
         }
     }
 
-    private boolean startPendingEnterAnimation(
+    private boolean startPendingEnterAnimation(@NonNull IBinder transition,
             @NonNull SplitScreenTransitions.EnterSession enterTransition,
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
             @NonNull SurfaceControl.Transaction finishT) {
@@ -2759,21 +2789,22 @@
             }
         }
 
-        if (mSplitTransitions.mPendingEnter.mExtraTransitType
+        SplitScreenTransitions.EnterSession pendingEnter = mSplitTransitions.mPendingEnter;
+        if (pendingEnter.mExtraTransitType
                 == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
             // Open to side should only be used when split already active and foregorund.
             if (mainChild == null && sideChild == null) {
                 Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
                         "Launched a task in split, but didn't receive any task in transition."));
                 // This should happen when the target app is already on front, so just cancel.
-                mSplitTransitions.mPendingEnter.cancel(null);
+                pendingEnter.cancel(null);
                 return true;
             }
         } else {
             if (mainChild == null || sideChild == null) {
                 final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN :
                         (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
-                mSplitTransitions.mPendingEnter.cancel(
+                pendingEnter.cancel(
                         (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
                 Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
                         "launched 2 tasks in split, but didn't receive "
@@ -2784,6 +2815,12 @@
                 if (mRecentTasks.isPresent() && sideChild != null) {
                     mRecentTasks.get().removeSplitPair(sideChild.getTaskInfo().taskId);
                 }
+                if (pendingEnter.mRemoteHandler != null) {
+                    // Pass false for aborted since WM didn't abort, business logic chose to
+                    // terminate/exit early
+                    pendingEnter.mRemoteHandler.onTransitionConsumed(transition,
+                            false /*aborted*/, finishT);
+                }
                 mSplitUnsupportedToast.show();
                 return true;
             }
@@ -2894,7 +2931,11 @@
         return SPLIT_POSITION_UNDEFINED;
     }
 
-    /** Synchronize split-screen state with transition and make appropriate preparations. */
+    /**
+     * Synchronize split-screen state with transition and make appropriate preparations.
+     * @param toStage The stage that will not be dismissed. If set to
+     *        {@link SplitScreen#STAGE_TYPE_UNDEFINED} then both stages will be dismissed
+     */
     public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
             @NonNull SurfaceControl.Transaction finishT) {
@@ -3109,6 +3150,7 @@
                 null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
     }
 
+    @NeverCompile
     @Override
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
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 e828eed..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
@@ -50,6 +50,7 @@
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.splitscreen.StageCoordinator;
 import com.android.wm.shell.sysui.ShellInit;
@@ -344,6 +345,8 @@
                     // Keyguard handler cannot handle it, process through original mixed
                     mActiveTransitions.remove(keyguardMixed);
                 }
+            } else if (mPipHandler != null) {
+                mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
             }
         }
 
@@ -511,8 +514,26 @@
             // make a new startTransaction because pip's startEnterAnimation "consumes" it so
             // we need a separate one to send over to launcher.
             SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
+            @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
+            if (mSplitHandler.isSplitScreenVisible()) {
+                // The non-going home case, we could be pip-ing one of the split stages and keep
+                // showing the other
+                for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                    TransitionInfo.Change change = info.getChanges().get(i);
+                    if (change == pipChange) {
+                        // Ignore the change/task that's going into Pip
+                        continue;
+                    }
+                    @SplitScreen.StageType int splitItemStage =
+                            mSplitHandler.getSplitItemStage(change.getLastParent());
+                    if (splitItemStage != STAGE_TYPE_UNDEFINED) {
+                        topStageToKeep = splitItemStage;
+                        break;
+                    }
+                }
+            }
             // Let split update internal state for dismiss.
-            mSplitHandler.prepareDismissAnimation(STAGE_TYPE_UNDEFINED,
+            mSplitHandler.prepareDismissAnimation(topStageToKeep,
                     EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
                     finishTransaction);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index fab2dd2..8b050e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -152,6 +152,16 @@
     }
 
     @Override
+    public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+            @Nullable SurfaceControl.Transaction finishTransaction) {
+        try {
+            mRemote.getRemoteTransition().onTransitionConsumed(transition, aborted);
+        } catch (RemoteException e) {
+            Log.e(Transitions.TAG, "Error calling onTransitionConsumed()", e);
+        }
+    }
+
+    @Override
     public String toString() {
         return "OneShotRemoteHandler:" + mRemote.getDebugName() + ":"
                 + mRemote.getRemoteTransition();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index a90edf2..592b22a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -86,7 +86,16 @@
     @Override
     public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
             @Nullable SurfaceControl.Transaction finishT) {
-        mRequestedRemotes.remove(transition);
+        RemoteTransition remoteTransition = mRequestedRemotes.remove(transition);
+        if (remoteTransition == null) {
+            return;
+        }
+
+        try {
+            remoteTransition.getRemoteTransition().onTransitionConsumed(transition, aborted);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error delegating onTransitionConsumed()", e);
+        }
     }
 
     @Override
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/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index c74b3f3..0d9a9e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -277,7 +277,7 @@
             @NonNull ShellExecutor animExecutor) {
         this(context, shellInit, shellController, organizer, pool, displayController, mainExecutor,
                 mainHandler, animExecutor, null,
-                new RootTaskDisplayAreaOrganizer(mainExecutor, context));
+                new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit));
     }
 
     public Transitions(@NonNull Context context,
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/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
index 09fc3da..368231e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
@@ -132,6 +132,13 @@
                 t.setAlpha(mVeilSurface, mVeilAnimator.getAnimatedFraction());
                 t.apply();
             });
+            mVeilAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    t.setAlpha(mVeilSurface, 1);
+                    t.apply();
+                }
+            });
 
             final ValueAnimator iconAnimator = new ValueAnimator();
             iconAnimator.setFloatValues(0f, 1f);
@@ -192,8 +199,8 @@
      */
     public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) {
         if (mVeilAnimator != null && mVeilAnimator.isStarted()) {
-            // TODO(b/300145351): Investigate why ValueAnimator#end does not work here.
-            mVeilAnimator.setCurrentPlayTime(RESIZE_ALPHA_DURATION);
+            mVeilAnimator.removeAllUpdateListeners();
+            mVeilAnimator.end();
         }
         relayout(newBounds, t);
         mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
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 6062e34..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;
@@ -44,6 +47,7 @@
 
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
 
 import java.util.function.Supplier;
 
@@ -118,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();
@@ -224,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();
@@ -271,22 +278,26 @@
 
             // 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);
         }
 
         // Task surface itself
         float shadowRadius = loadDimension(resources, params.mShadowRadiusId);
-        int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
-        mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
-        mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
-        mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
         final Point taskPosition = mTaskInfo.positionInParent;
         if (isFullscreen) {
             // Setting the task crop to the width/height stops input events from being sent to
@@ -302,13 +313,22 @@
             finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
         }
         startT.setShadowRadius(mTaskSurface, shadowRadius)
-                .setColor(mTaskSurface, mTmpColor)
                 .show(mTaskSurface);
         finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
                 .setShadowRadius(mTaskSurface, shadowRadius);
         if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+            if (!DesktopModeStatus.isVeiledResizeEnabled()) {
+                // When fluid resize is enabled, add a background to freeform tasks
+                int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
+                mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
+                mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
+                mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
+                startT.setColor(mTaskSurface, mTmpColor);
+            }
             startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
             finishT.setCornerRadius(mTaskSurface, params.mCornerRadius);
+        } else if (!DesktopModeStatus.isVeiledResizeEnabled()) {
+            startT.unsetColor(mTaskSurface);
         }
 
         if (mCaptionWindowManager == null) {
@@ -342,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.
@@ -460,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/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 0058d11..7f02072 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -116,6 +116,7 @@
         "wm-flicker-common-assertions",
         "launcher-helper-lib",
         "launcher-aosp-tapl",
+        "com_android_wm_shell_flags_lib",
     ],
 }
 
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/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index 6748626..0fd1b2c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -18,6 +18,7 @@
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
+import android.tools.common.flicker.subject.exceptions.IncorrectRegionException
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
@@ -40,14 +41,26 @@
         transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) }
     }
 
-    /** Checks that the visible region area of [pipApp] always decreases during the animation. */
+    /**
+     * Checks that the visible region area of [pipApp] decreases
+     * and then increases during the animation.
+     */
     @Presubmit
     @Test
-    fun pipLayerAreaDecreases() {
+    fun pipLayerAreaDecreasesThenIncreases() {
+        val isAreaDecreasing = arrayOf(true)
         flicker.assertLayers {
             val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
             pipLayerList.zipWithNext { previous, current ->
-                current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
+                if (isAreaDecreasing[0]) {
+                    try {
+                        current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
+                    } catch (e: IncorrectRegionException) {
+                        isAreaDecreasing[0] = false
+                    }
+                } else {
+                    previous.visibleRegion.notBiggerThan(current.visibleRegion.region)
+                }
             }
         }
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index d09a90c..aadadd6 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -35,6 +35,7 @@
     static_libs: [
         "WindowManager-Shell",
         "junit",
+        "flag-junit-base",
         "androidx.test.runner",
         "androidx.test.rules",
         "androidx.test.ext.junit",
@@ -49,6 +50,7 @@
         "testables",
         "platform-test-annotations",
         "servicestests-utils",
+        "com_android_wm_shell_flags_lib",
     ],
 
     libs: [
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
index 58d9a64..287a97c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
@@ -22,20 +22,24 @@
 import static android.view.View.LAYOUT_DIRECTION_RTL;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
 
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Insets;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableResources;
+import android.util.DisplayMetrics;
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowMetrics;
@@ -257,6 +261,27 @@
         assertThat(mPositioner.hasUserModifiedDefaultPosition()).isTrue();
     }
 
+    @Test
+    public void testExpandedViewHeight_onLargeTablet() {
+        Insets insets = Insets.of(10, 20, 5, 15);
+        Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+        new WindowManagerConfig()
+                .setLargeScreen()
+                .setInsets(insets)
+                .setScreenBounds(screenBounds)
+                .setUpConfig();
+        mPositioner.update();
+
+        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+        int manageButtonHeight =
+                mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height);
+        float expectedHeight = 1800 - 2 * 20 - manageButtonHeight;
+        assertThat(mPositioner.getExpandedViewHeight(bubble)).isWithin(0.1f).of(expectedHeight);
+    }
+
     /**
      * Calculates the Y position bubbles should be placed based on the config. Based on
      * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and
@@ -323,6 +348,8 @@
                     ? MIN_WIDTH_FOR_TABLET
                     : MIN_WIDTH_FOR_TABLET - 1;
             mConfiguration.orientation = mOrientation;
+            mConfiguration.screenWidthDp = pxToDp(mScreenBounds.width());
+            mConfiguration.screenHeightDp = pxToDp(mScreenBounds.height());
 
             when(mConfiguration.getLayoutDirection()).thenReturn(mLayoutDirection);
             WindowInsets windowInsets = mock(WindowInsets.class);
@@ -331,5 +358,11 @@
             when(mWindowMetrics.getBounds()).thenReturn(mScreenBounds);
             when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
         }
+
+        private int pxToDp(float px) {
+            int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
+            float dp = px / ((float) dpi / DisplayMetrics.DENSITY_DEFAULT);
+            return (int) dp;
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index d34e27b..db98abb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.ComponentName;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -60,6 +61,9 @@
     /** The minimum possible size of the override min size's width or height */
     private static final int OVERRIDABLE_MIN_SIZE = 40;
 
+    /** The margin of error for floating point results. */
+    private static final float MARGIN_OF_ERROR = 0.05f;
+
     private PipBoundsState mPipBoundsState;
     private SizeSpecSource mSizeSpecSource;
     private ComponentName mTestComponentName1;
@@ -88,6 +92,27 @@
     }
 
     @Test
+    public void testBoundsScale() {
+        mPipBoundsState.setMaxSize(300, 300);
+        mPipBoundsState.setBounds(new Rect(0, 0, 100, 100));
+
+        final int currentWidth = mPipBoundsState.getBounds().width();
+        final Point maxSize = mPipBoundsState.getMaxSize();
+        final float expectedBoundsScale = Math.min((float) currentWidth / maxSize.x, 1.0f);
+
+        // test for currentWidth < maxWidth
+        assertEquals(expectedBoundsScale, mPipBoundsState.getBoundsScale(), MARGIN_OF_ERROR);
+
+        // reset the bounds to be at the maximum size spec
+        mPipBoundsState.setBounds(new Rect(0, 0, maxSize.x, maxSize.y));
+        assertEquals(1.0f, mPipBoundsState.getBoundsScale(), /* delta */ 0f);
+
+        // reset the bounds to be over the maximum size spec
+        mPipBoundsState.setBounds(new Rect(0, 0, maxSize.x * 2, maxSize.y * 2));
+        assertEquals(1.0f, mPipBoundsState.getBoundsScale(), /* delta */ 0f);
+    }
+
+    @Test
     public void testSetReentryState() {
         final Size size = new Size(100, 100);
         final float snapFraction = 0.5f;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 6777a5b..9719ba8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -28,11 +28,13 @@
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.testing.TestableResources;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
@@ -98,6 +100,9 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        final TestableResources res = mContext.getOrCreateTestableResources();
+        res.addOverride(R.bool.config_pipEnablePinchResize, true);
+
         mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
         mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
         mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index ebc284b..befc702 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -208,6 +208,30 @@
 
     @Test
     @UiThreadTest
+    public void testRemoteTransitionConsumed() {
+        // Omit side child change
+        TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+                .addChange(TRANSIT_OPEN, mMainChild)
+                .build();
+        TestRemoteTransition testRemote = new TestRemoteTransition();
+
+        IBinder transition = mSplitScreenTransitions.startEnterTransition(
+                TRANSIT_OPEN, new WindowContainerTransaction(),
+                new RemoteTransition(testRemote, "Test"), mStageCoordinator,
+                TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
+        mMainStage.onTaskAppeared(mMainChild, createMockSurface());
+        boolean accepted = mStageCoordinator.startAnimation(transition, info,
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                mock(Transitions.TransitionFinishCallback.class));
+        assertTrue(accepted);
+
+        assertTrue(testRemote.isConsumed());
+
+    }
+
+    @Test
+    @UiThreadTest
     public void testMonitorInSplit() {
         enterSplit();
 
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 d598678..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
@@ -50,6 +50,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
@@ -92,6 +93,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayController;
@@ -145,7 +147,9 @@
         final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
                 mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
                 mMainHandler, mAnimExecutor);
-        verify(shellInit, times(1)).addInitCallback(any(), eq(t));
+        // One from Transitions, one from RootTaskDisplayAreaOrganizer
+        verify(shellInit).addInitCallback(any(), eq(t));
+        verify(shellInit).addInitCallback(any(), isA(RootTaskDisplayAreaOrganizer.class));
     }
 
     @Test
@@ -285,6 +289,10 @@
                     SurfaceControl.Transaction t, IBinder mergeTarget,
                     IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
             }
+
+            @Override
+            public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
+            }
         };
         IBinder transitToken = new Binder();
         transitions.requestStartTransition(transitToken,
@@ -427,6 +435,10 @@
                     SurfaceControl.Transaction t, IBinder mergeTarget,
                     IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
             }
+
+            @Override
+            public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
+            }
         };
 
         TransitionFilter filter = new TransitionFilter();
@@ -473,6 +485,10 @@
                     SurfaceControl.Transaction t, IBinder mergeTarget,
                     IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
             }
+
+            @Override
+            public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
+            }
         };
 
         final int transitType = TRANSIT_FIRST_CUSTOM + 1;
@@ -1153,7 +1169,7 @@
     }
 
     @Test
-    public void testEmptyTransitionStillReportsKeyguardGoingAway() {
+    public void testEmptyTransition_withKeyguardGoingAway_plays() {
         Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
 
@@ -1172,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/transition/TestRemoteTransition.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
index 39ab238..87330d2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
@@ -31,6 +31,7 @@
  */
 public class TestRemoteTransition extends IRemoteTransition.Stub {
     private boolean mCalled = false;
+    private boolean mConsumed = false;
     final WindowContainerTransaction mRemoteFinishWCT = new WindowContainerTransaction();
 
     @Override
@@ -48,6 +49,11 @@
             IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
     }
 
+    @Override
+    public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
+        mConsumed = true;
+    }
+
     /**
      * Check whether this remote transition
      * {@link #startAnimation(IBinder, TransitionInfo, SurfaceControl.Transaction,
@@ -56,4 +62,12 @@
     public boolean isCalled() {
         return mCalled;
     }
+
+    /**
+     * Check whether this remote transition's {@link #onTransitionConsumed(IBinder, boolean)}
+     * is called
+     */
+    public boolean isConsumed() {
+        return mConsumed;
+    }
 }
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 966a99ee..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
@@ -17,12 +17,19 @@
 package com.android.wm.shell.windowdecor;
 
 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;
 import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction;
 
 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;
@@ -36,6 +43,7 @@
 import static org.mockito.Mockito.same;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.quality.Strictness.LENIENT;
 
 import android.app.ActivityManager;
 import android.content.Context;
@@ -48,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;
@@ -59,10 +68,12 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.tests.R;
 
 import org.junit.Before;
@@ -89,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<>();
@@ -113,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;
@@ -136,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
@@ -201,12 +219,8 @@
                 createMockSurfaceControlBuilder(captionContainerSurface);
         mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
 
-        final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
-                new ActivityManager.TaskDescription.Builder()
-                        .setBackgroundColor(Color.YELLOW);
         final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
                 .setDisplayId(Display.DEFAULT_DISPLAY)
-                .setTaskDescriptionBuilder(taskDescriptionBuilder)
                 .setBounds(TASK_BOUNDS)
                 .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
                 .setVisible(true)
@@ -255,8 +269,6 @@
         verify(mMockSurfaceControlFinishT).setCornerRadius(taskSurface, CORNER_RADIUS);
         verify(mMockSurfaceControlStartT)
                 .show(taskSurface);
-        verify(mMockSurfaceControlStartT)
-                .setColor(taskSurface, new float[] {1.f, 1.f, 0.f});
         verify(mMockSurfaceControlStartT).setShadowRadius(taskSurface, 10);
 
         assertEquals(300, mRelayoutResult.mWidth);
@@ -502,6 +514,142 @@
         verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT);
     }
 
+    @Test
+    public void testRelayout_fluidResizeEnabled_freeformTask_setTaskSurfaceColor() {
+        StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
+                DesktopModeStatus.class).strictness(
+                LENIENT).startMocking();
+        when(DesktopModeStatus.isVeiledResizeEnabled()).thenReturn(false);
+
+        final Display defaultDisplay = mock(Display.class);
+        doReturn(defaultDisplay).when(mMockDisplayController)
+                .getDisplay(Display.DEFAULT_DISPLAY);
+
+        final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+        final SurfaceControl.Builder decorContainerSurfaceBuilder =
+                createMockSurfaceControlBuilder(decorContainerSurface);
+        mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+        final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
+        final SurfaceControl.Builder captionContainerSurfaceBuilder =
+                createMockSurfaceControlBuilder(captionContainerSurface);
+        mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
+
+        final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+                new ActivityManager.TaskDescription.Builder()
+                        .setBackgroundColor(Color.YELLOW);
+
+        final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .setTaskDescriptionBuilder(taskDescriptionBuilder)
+                .setVisible(true)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM)
+                .build();
+        taskInfo.isFocused = true;
+        final SurfaceControl taskSurface = mock(SurfaceControl.class);
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+
+        windowDecor.relayout(taskInfo);
+
+        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();
+        when(DesktopModeStatus.isVeiledResizeEnabled()).thenReturn(false);
+
+        final Display defaultDisplay = mock(Display.class);
+        doReturn(defaultDisplay).when(mMockDisplayController)
+                .getDisplay(Display.DEFAULT_DISPLAY);
+
+        final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+        final SurfaceControl.Builder decorContainerSurfaceBuilder =
+                createMockSurfaceControlBuilder(decorContainerSurface);
+        mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+        final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
+        final SurfaceControl.Builder captionContainerSurfaceBuilder =
+                createMockSurfaceControlBuilder(captionContainerSurface);
+        mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
+
+        final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+                new ActivityManager.TaskDescription.Builder()
+                        .setBackgroundColor(Color.YELLOW);
+        final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .setTaskDescriptionBuilder(taskDescriptionBuilder)
+                .setVisible(true)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .build();
+        taskInfo.isFocused = true;
+        final SurfaceControl taskSurface = mock(SurfaceControl.class);
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+
+        windowDecor.relayout(taskInfo);
+
+        verify(mMockSurfaceControlStartT).unsetColor(taskSurface);
+
+        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/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 7aef7a5..1463945 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -593,7 +593,7 @@
         return result;
     }
 
-    static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics *metrics) {
+    static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics* metrics, bool useLocale) {
         const int kElegantTop = 2500;
         const int kElegantBottom = -1000;
         const int kElegantAscent = 1900;
@@ -622,6 +622,17 @@
             metrics->fLeading = size * kElegantLeading / 2048;
             spacing = metrics->fDescent - metrics->fAscent + metrics->fLeading;
         }
+
+        if (useLocale) {
+            minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
+            minikin::MinikinExtent extent =
+                    typeface->fFontCollection->getReferenceExtentForLocale(minikinPaint);
+            metrics->fAscent = std::min(extent.ascent, metrics->fAscent);
+            metrics->fDescent = std::max(extent.descent, metrics->fDescent);
+            metrics->fTop = std::min(metrics->fAscent, metrics->fTop);
+            metrics->fBottom = std::max(metrics->fDescent, metrics->fBottom);
+        }
+
         return spacing;
     }
 
@@ -634,7 +645,7 @@
                 MinikinUtils::getFontExtent(paint, bidiFlags, typeface, buf, start, count, bufSize);
 
         SkFontMetrics metrics;
-        getMetricsInternal(paintHandle, &metrics);
+        getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
 
         metrics.fAscent = extent.ascent;
         metrics.fDescent = extent.descent;
@@ -686,20 +697,21 @@
         }
     }
 
-    static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
+    static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj,
+                                 jboolean useLocale) {
         SkFontMetrics metrics;
-        SkScalar spacing = getMetricsInternal(paintHandle, &metrics);
+        SkScalar spacing = getMetricsInternal(paintHandle, &metrics, useLocale);
         GraphicsJNI::set_metrics(env, metricsObj, metrics);
         return SkScalarToFloat(spacing);
     }
 
-    static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
+    static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj,
+                                  jboolean useLocale) {
         SkFontMetrics metrics;
-        getMetricsInternal(paintHandle, &metrics);
+        getMetricsInternal(paintHandle, &metrics, useLocale);
         return GraphicsJNI::set_metrics_int(env, metricsObj, metrics);
     }
 
-
     // ------------------ @CriticalNative ---------------------------
 
     static void reset(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) {
@@ -1002,19 +1014,19 @@
 
     static jfloat ascent(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
         SkFontMetrics metrics;
-        getMetricsInternal(paintHandle, &metrics);
+        getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
         return SkScalarToFloat(metrics.fAscent);
     }
 
     static jfloat descent(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
         SkFontMetrics metrics;
-        getMetricsInternal(paintHandle, &metrics);
+        getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
         return SkScalarToFloat(metrics.fDescent);
     }
 
     static jfloat getUnderlinePosition(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
         SkFontMetrics metrics;
-        getMetricsInternal(paintHandle, &metrics);
+        getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
         SkScalar position;
         if (metrics.hasUnderlinePosition(&position)) {
             return SkScalarToFloat(position);
@@ -1026,7 +1038,7 @@
 
     static jfloat getUnderlineThickness(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
         SkFontMetrics metrics;
-        getMetricsInternal(paintHandle, &metrics);
+        getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
         SkScalar thickness;
         if (metrics.hasUnderlineThickness(&thickness)) {
             return SkScalarToFloat(thickness);
@@ -1121,9 +1133,9 @@
         {"nSetTextLocales", "(JLjava/lang/String;)I", (void*)PaintGlue::setTextLocales},
         {"nSetFontFeatureSettings", "(JLjava/lang/String;)V",
          (void*)PaintGlue::setFontFeatureSettings},
-        {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F",
+        {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;Z)F",
          (void*)PaintGlue::getFontMetrics},
-        {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I",
+        {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;Z)I",
          (void*)PaintGlue::getFontMetricsInt},
 
         // --------------- @CriticalNative ------------------
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/location/java/android/location/GnssSignalType.java b/location/java/android/location/GnssSignalType.java
index 16c3f2e..2dd67f0 100644
--- a/location/java/android/location/GnssSignalType.java
+++ b/location/java/android/location/GnssSignalType.java
@@ -34,8 +34,7 @@
     /**
      * Creates a {@link GnssSignalType} with a full list of parameters.
      *
-     * @param constellationType the constellation type as defined in
-     * {@link GnssStatus.ConstellationType}
+     * @param constellationType the constellation type
      * @param carrierFrequencyHz the carrier frequency in Hz
      * @param codeType the code type as defined in {@link GnssMeasurement#getCodeType()}
      */
@@ -66,7 +65,7 @@
         this.mCodeType = codeType;
     }
 
-    /** Returns the {@link GnssStatus.ConstellationType}. */
+    /** Returns the constellation type. */
     @GnssStatus.ConstellationType
     public int getConstellationType() {
         return mConstellationType;
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 230fb07..bf9419fe 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -875,18 +875,7 @@
         /**
          * Sets the attribute describing what is the intended use of the audio signal,
          * such as alarm or ringtone.
-         * @param usage one of {@link AttributeSdkUsage#USAGE_UNKNOWN},
-         *     {@link AttributeSdkUsage#USAGE_MEDIA},
-         *     {@link AttributeSdkUsage#USAGE_VOICE_COMMUNICATION},
-         *     {@link AttributeSdkUsage#USAGE_VOICE_COMMUNICATION_SIGNALLING},
-         *     {@link AttributeSdkUsage#USAGE_ALARM}, {@link AudioAttributes#USAGE_NOTIFICATION},
-         *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_RINGTONE},
-         *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_EVENT},
-         *     {@link AttributeSdkUsage#USAGE_ASSISTANT},
-         *     {@link AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY},
-         *     {@link AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE},
-         *     {@link AttributeSdkUsage#USAGE_ASSISTANCE_SONIFICATION},
-         *     {@link AttributeSdkUsage#USAGE_GAME}.
+         * @param usage the usage to set.
          * @return the same Builder instance.
          */
         public Builder setUsage(@AttributeSdkUsage int usage) {
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index a311296..b002bbf 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -1282,11 +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 {@link AudioSystem#OUT_CHANNEL_COUNT_MAX} 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 adc0e16..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) {
@@ -4659,24 +4658,24 @@
         Objects.requireNonNull(afr);
         Objects.requireNonNull(clientFakeId);
         int status;
-        try {
-            status = getService().requestAudioFocusForTest(afr.getAudioAttributes(),
-                    afr.getFocusGain(),
-                    mICallBack,
-                    mAudioFocusDispatcher,
-                    clientFakeId, "com.android.test.fakeclient",
-                    afr.getFlags() | AudioManager.AUDIOFOCUS_FLAG_TEST,
-                    clientFakeUid, clientTargetSdk);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-        if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
-            // default path with no external focus policy
-            return status;
-        }
-
         BlockingFocusResultReceiver focusReceiver;
         synchronized (mFocusRequestsLock) {
+            try {
+                status = getService().requestAudioFocusForTest(afr.getAudioAttributes(),
+                        afr.getFocusGain(),
+                        mICallBack,
+                        mAudioFocusDispatcher,
+                        clientFakeId, "com.android.test.fakeclient",
+                        afr.getFlags() | AudioManager.AUDIOFOCUS_FLAG_TEST,
+                        clientFakeUid, clientTargetSdk);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
+                // default path with no external focus policy
+                return status;
+            }
+
             focusReceiver = addClientIdToFocusReceiverLocked(clientFakeId);
         }
 
diff --git a/media/java/android/media/AudioMetadata.java b/media/java/android/media/AudioMetadata.java
index 0f962f9..4e61549 100644
--- a/media/java/android/media/AudioMetadata.java
+++ b/media/java/android/media/AudioMetadata.java
@@ -226,16 +226,15 @@
          *
          * An Integer value representing presentation content classifier.
          *
-         * @see AudioPresentation.ContentClassifier
-         * One of {@link AudioPresentation#CONTENT_UNKNOWN},
-         *     {@link AudioPresentation#CONTENT_MAIN},
-         *     {@link AudioPresentation#CONTENT_MUSIC_AND_EFFECTS},
-         *     {@link AudioPresentation#CONTENT_VISUALLY_IMPAIRED},
-         *     {@link AudioPresentation#CONTENT_HEARING_IMPAIRED},
-         *     {@link AudioPresentation#CONTENT_DIALOG},
-         *     {@link AudioPresentation#CONTENT_COMMENTARY},
-         *     {@link AudioPresentation#CONTENT_EMERGENCY},
-         *     {@link AudioPresentation#CONTENT_VOICEOVER}.
+         * @see AudioPresentation#CONTENT_UNKNOWN
+         * @see AudioPresentation#CONTENT_MAIN
+         * @see AudioPresentation#CONTENT_MUSIC_AND_EFFECTS
+         * @see AudioPresentation#CONTENT_VISUALLY_IMPAIRED
+         * @see AudioPresentation#CONTENT_HEARING_IMPAIRED
+         * @see AudioPresentation#CONTENT_DIALOG
+         * @see AudioPresentation#CONTENT_COMMENTARY
+         * @see AudioPresentation#CONTENT_EMERGENCY
+         * @see AudioPresentation#CONTENT_VOICEOVER
          */
         @NonNull public static final Key<Integer> KEY_PRESENTATION_CONTENT_CLASSIFIER =
                 createKey("presentation-content-classifier", Integer.class);
diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl
index 73f15f2..1e57be2 100644
--- a/media/java/android/media/IRingtonePlayer.aidl
+++ b/media/java/android/media/IRingtonePlayer.aidl
@@ -49,7 +49,7 @@
     oneway void setHapticGeneratorEnabled(IBinder token, boolean hapticGeneratorEnabled);
 
     /** Used for Notification sound playback. */
-    oneway void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa);
+    oneway void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa, float volume);
     oneway void stopAsync();
 
     /** Return the title of the media. */
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/RingtoneSelection.java b/media/java/android/media/RingtoneSelection.java
index 74f7276..b74b6a3 100644
--- a/media/java/android/media/RingtoneSelection.java
+++ b/media/java/android/media/RingtoneSelection.java
@@ -18,16 +18,23 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
+import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.net.Uri;
+import android.os.UserHandle;
+import android.os.vibrator.Flags;
 import android.provider.MediaStore;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 
 /**
  * Immutable representation a desired ringtone, usually originating from a user preference.
@@ -46,7 +53,7 @@
  * to be internally consistent and reflect effective values - with the exception of not verifying
  * the actual URI content. For example, loading a selection Uri that sets a sound source to
  * {@link #SOUND_SOURCE_URI}, but doesn't also have a sound Uri set, will result in this class
- * instead returning {@link #SOUND_SOURCE_DEFAULT} from {@link #getSoundSource}.
+ * instead returning {@link #SOUND_SOURCE_UNSPECIFIED} from {@link #getSoundSource}.
  *
  * <h2>Storing preferences</h2>
  *
@@ -57,6 +64,7 @@
  * @hide
  */
 @TestApi
+@FlaggedApi(Flags.FLAG_HAPTICS_CUSTOMIZATION_ENABLED)
 public final class RingtoneSelection {
 
     /**
@@ -70,7 +78,7 @@
      * The sound source is not explicitly specified, so it can follow default behavior for its
      * context.
      */
-    public static final int SOUND_SOURCE_DEFAULT = 0;
+    public static final int SOUND_SOURCE_UNSPECIFIED = 0;
 
     /**
      * Sound is explicitly disabled, such as the user having selected "Silent" in the sound picker.
@@ -83,15 +91,25 @@
     public static final int SOUND_SOURCE_URI = 2;
 
     /**
+     * The sound should explicitly use the system default.
+     *
+     * <p>This value isn't valid within the system default itself.
+     */
+    public static final int SOUND_SOURCE_SYSTEM_DEFAULT = 3;
+
+    // Note: Value 4 reserved for possibility of SOURCE_SOURCE_APPLICATION_DEFAULT.
+
+    /**
      * Directive for how to make sound.
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "SOUND_SOURCE_", value = {
             SOUND_SOURCE_UNKNOWN,
-            SOUND_SOURCE_DEFAULT,
+            SOUND_SOURCE_UNSPECIFIED,
             SOUND_SOURCE_OFF,
             SOUND_SOURCE_URI,
+            SOUND_SOURCE_SYSTEM_DEFAULT,
     })
     public @interface SoundSource {}
 
@@ -106,9 +124,9 @@
     /**
      * Vibration source is not explicitly specified. If vibration is enabled, this will use the
      * first available of {@link #VIBRATION_SOURCE_AUDIO_CHANNEL},
-     * {@link #VIBRATION_SOURCE_APPLICATION_PROVIDED}, or system default vibration.
+     * {@link #VIBRATION_SOURCE_APPLICATION_DEFAULT}, or {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}.
      */
-    public static final int VIBRATION_SOURCE_DEFAULT = 0;
+    public static final int VIBRATION_SOURCE_UNSPECIFIED = 0;
 
     /** Specifies that vibration is explicitly disabled for this ringtone. */
     public static final int VIBRATION_SOURCE_OFF = 1;
@@ -117,22 +135,31 @@
     public static final int VIBRATION_SOURCE_URI = 2;
 
     /**
+     * The vibration should explicitly use the system default.
+     *
+     * <p>This value isn't valid within the system default itself.
+     */
+    public static final int VIBRATION_SOURCE_SYSTEM_DEFAULT = 3;
+
+    /**
      * Specifies that vibration should use the vibration provided by the application. This is
      * typically the application's own default for the use-case, provided via
      * {@link Ringtone.Builder#setVibrationEffect}. For notification channels, this is the vibration
      * effect saved on the notification channel.
      *
      * <p>If no vibration is specified by the application, this value behaves if the source was
-     * {@link #VIBRATION_SOURCE_DEFAULT}.
+     * {@link #VIBRATION_SOURCE_UNSPECIFIED}.
+     *
+     * <p>This value isn't valid within the system default.
      */
-    public static final int VIBRATION_SOURCE_APPLICATION_PROVIDED = 3;
+    public static final int VIBRATION_SOURCE_APPLICATION_DEFAULT = 4;
 
     /**
      * Specifies that vibration should use haptic audio channels from the
      * sound Uri. If the sound URI doesn't have haptic channels, then reverts to the order specified
-     * by {@link #VIBRATION_SOURCE_DEFAULT}.
+     * by {@link #VIBRATION_SOURCE_UNSPECIFIED}.
      */
-    // Numeric gap from VIBRATION_SOURCE_APPLICATION_PROVIDED in case we want other common elements.
+    // Numeric gap from VIBRATION_SOURCE_APPLICATION_DEFAULT in case we want other common elements.
     public static final int VIBRATION_SOURCE_AUDIO_CHANNEL = 10;
 
     /**
@@ -151,10 +178,10 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "VIBRATION_SOURCE_", value = {
             VIBRATION_SOURCE_UNKNOWN,
-            VIBRATION_SOURCE_DEFAULT,
+            VIBRATION_SOURCE_UNSPECIFIED,
             VIBRATION_SOURCE_OFF,
             VIBRATION_SOURCE_URI,
-            VIBRATION_SOURCE_APPLICATION_PROVIDED,
+            VIBRATION_SOURCE_APPLICATION_DEFAULT,
             VIBRATION_SOURCE_AUDIO_CHANNEL,
             VIBRATION_SOURCE_HAPTIC_GENERATOR,
     })
@@ -162,9 +189,12 @@
 
     /**
      * Configures {@link #RingtoneSelection#fromUri} to treat an unrecognized Uri as the sound Uri
-     * for the returned {@link RingtoneSelection}, with null meaning {@link #SOUND_SOURCE_OFF}.
-     * This behavior is particularly suited to loading values from older settings that may contain
-     * a raw sound Uri or null for silent.
+     * for the returned {@link RingtoneSelection}, with null meaning {@link #SOUND_SOURCE_OFF},
+     * and symbolic default URIs ({@link RingtoneManager#getDefaultUri}) meaning
+     * {@link #SOUND_SOURCE_SYSTEM_DEFAULT}.
+     *
+     * <p>This behavior is particularly suited to loading values from older settings that may
+     * contain a raw sound Uri or null for silent.
      *
      * <p>An unrecognized Uri is one for which {@link #isRingtoneSelectionUri(Uri)} returns false.
      */
@@ -173,7 +203,8 @@
     /**
      * Configures {@link #RingtoneSelection#fromUri} to treat an unrecognized Uri as the vibration
      * Uri for the returned {@link RingtoneSelection}, with null meaning
-     * {@link #VIBRATION_SOURCE_OFF}.
+     * {@link #VIBRATION_SOURCE_OFF} and symbolic default URIs
+     * ({@link RingtoneManager#getDefaultUri}) meaning {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}.
      *
      * <p>An unrecognized Uri is one for which {@link #isRingtoneSelectionUri(Uri)} returns false.
      */
@@ -182,7 +213,9 @@
     /**
      * Configures {@link #RingtoneSelection#fromUri} to treat an unrecognized Uri as an invalid
      * value. Null or an invalid values will revert to default behavior correspnoding to
-     * {@link #DEFAULT_SELECTION_URI_STRING}.
+     * {@link #DEFAULT_SELECTION_URI_STRING}. Symbolic default URIs
+     * ({@link RingtoneManager#getDefaultUri}) will set both
+     * {@link #SOUND_SOURCE_SYSTEM_DEFAULT} and {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}.
      *
      * <p>An unrecognized Uri is one for which {@link #isRingtoneSelectionUri(Uri)} returns false,
      * which include {@code null}.
@@ -218,10 +251,11 @@
 
     /* Common param values */
     private static final String SOURCE_OFF_STRING = "off";
+    private static final String SOURCE_SYSTEM_DEFAULT_STRING = "sys";
 
     /* Vibration source param values. */
     private static final String VIBRATION_SOURCE_AUDIO_CHANNEL_STRING = "ac";
-    private static final String VIBRATION_SOURCE_APPLICATION_PROVIDED_STRING = "app";
+    private static final String VIBRATION_SOURCE_APPLICATION_DEFAULT_STRING = "app";
     private static final String VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING = "hg";
 
     @Nullable
@@ -236,53 +270,45 @@
 
     private RingtoneSelection(@Nullable Uri soundUri, @SoundSource int soundSource,
             @Nullable Uri vibrationUri, @VibrationSource int vibrationSource) {
-        // Enforce guarantees on the source values: revert to unset if they depend on something
-        // that's not set.
-        switch (soundSource) {
-            case SOUND_SOURCE_URI:
-            case SOUND_SOURCE_UNKNOWN:  // Allow unknown to revert to URI before default.
-                mSoundSource = soundUri != null ? SOUND_SOURCE_URI : SOUND_SOURCE_DEFAULT;
-                break;
-            default:
-                mSoundSource = soundSource;
-                break;
-        }
-        switch (vibrationSource) {
-            case VIBRATION_SOURCE_AUDIO_CHANNEL:
-            case VIBRATION_SOURCE_HAPTIC_GENERATOR:
-                mVibrationSource = soundUri != null ? vibrationSource : VIBRATION_SOURCE_DEFAULT;
-                break;
-            case VIBRATION_SOURCE_URI:
-            case VIBRATION_SOURCE_UNKNOWN:  // Allow unknown to revert to URI.
-                mVibrationSource =
-                        vibrationUri != null ? VIBRATION_SOURCE_URI : VIBRATION_SOURCE_DEFAULT;
-                break;
-            default:
-                mVibrationSource = vibrationSource;
-                break;
-        }
+        // Enforce guarantees on the source values: revert to unspecified if they depend on
+        // something that's not set.
+        //
+        // The non-public "unknown" value can't appear in a getter result, it's just a reserved
+        // "null" value and should be treated the same as an unrecognized value. This can be seen
+        // in Uri parsing. For this and other unrecognized values, we either revert them to the URI
+        // source, if a Uri was included, or the "unspecified" source otherwise. This can be
+        // seen in action in the Uri parsing.
+        //
+        // The "unspecified" source is a public value meaning that there is no specific
+        // behavior indicated, and the defaults and fallbacks should be applied. For example, an
+        // vibration source value of "system default" means to explicitly use the system default
+        // vibration. However, an "unspecified" vibration source will first see if audio coupled
+        // or application-default vibrations are available.
+        mSoundSource = switch (soundSource) {
+            // Supported explicit values that don't have a Uri.
+            case SOUND_SOURCE_OFF, SOUND_SOURCE_UNSPECIFIED, SOUND_SOURCE_SYSTEM_DEFAULT ->
+                    soundSource;
+            // Uri and unknown/unrecognized values: use a Uri if one is present, else revert to
+            // unspecified.
+            default ->
+                soundUri != null ? SOUND_SOURCE_URI : SOUND_SOURCE_UNSPECIFIED;
+        };
+        mVibrationSource = switch (vibrationSource) {
+            // Enforce vibration sources that require a sound Uri.
+            case VIBRATION_SOURCE_AUDIO_CHANNEL, VIBRATION_SOURCE_HAPTIC_GENERATOR ->
+                    soundUri != null ? vibrationSource : VIBRATION_SOURCE_UNSPECIFIED;
+            // Supported explicit values that don't rely on any Uri.
+            case VIBRATION_SOURCE_OFF, VIBRATION_SOURCE_UNSPECIFIED,
+                    VIBRATION_SOURCE_SYSTEM_DEFAULT, VIBRATION_SOURCE_APPLICATION_DEFAULT ->
+                    vibrationSource;
+            // Uri and unknown/unrecognized values: use a Uri if one is present, else revert to
+            // unspecified.
+            default ->
+                    vibrationUri != null ? VIBRATION_SOURCE_URI : VIBRATION_SOURCE_UNSPECIFIED;
+        };
         // Clear Uri values if they're un-used by the source.
-        switch (mSoundSource) {
-            case SOUND_SOURCE_OFF:
-                mSoundUri = null;
-                break;
-            default:
-                // Unset case isn't handled here: the defaulting behavior is left to the player.
-                mSoundUri = soundUri;
-                break;
-        }
-        switch (mVibrationSource) {
-            case VIBRATION_SOURCE_OFF:
-            case VIBRATION_SOURCE_APPLICATION_PROVIDED:
-            case VIBRATION_SOURCE_AUDIO_CHANNEL:
-            case VIBRATION_SOURCE_HAPTIC_GENERATOR:
-                mVibrationUri = null;
-                break;
-            default:
-                // Unset case isn't handled here: the defaulting behavior is left to the player.
-                mVibrationUri = vibrationUri;
-                break;
-        }
+        mSoundUri = mSoundSource == SOUND_SOURCE_URI ? soundUri : null;
+        mVibrationUri = mVibrationSource == VIBRATION_SOURCE_URI ? vibrationUri : null;
     }
 
     /**
@@ -360,18 +386,83 @@
         }
         // Any URI content://media/ringtone
         return ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
-                && MediaStore.AUTHORITY.equals(uri.getAuthority())
+                && MediaStore.AUTHORITY.equals(
+                        ContentProvider.getAuthorityWithoutUserId(uri.getAuthority()))
                 && MEDIA_URI_RINGTONE_PATH.equals(uri.getPath());
     }
 
+    /**
+     * Strip the specified userId from internal Uris. Non-stripped userIds will typically be
+     * for work profiles referencing system ringtones from the host user.
+     *
+     * This is only for use in RingtoneManager.
+     * @hide
+     */
+    @VisibleForTesting
+    public RingtoneSelection getWithoutUserId(int userIdToStrip) {
+        if (mSoundSource != SOUND_SOURCE_URI && mVibrationSource != VIBRATION_SOURCE_URI) {
+            return this;
+        }
 
+        // Ok if uri is null. We only replace explicit references to the specified (current) userId.
+        int soundUserId = ContentProvider.getUserIdFromUri(mSoundUri, UserHandle.USER_NULL);
+        int vibrationUserId = ContentProvider.getUserIdFromUri(mVibrationUri, UserHandle.USER_NULL);
+        boolean needToChangeSound =
+                soundUserId != UserHandle.USER_NULL && soundUserId == userIdToStrip;
+        boolean needToChangeVibration =
+                vibrationUserId != UserHandle.USER_NULL && vibrationUserId == userIdToStrip;
+
+        // Anything to do?
+        if (!needToChangeSound && !needToChangeVibration) {
+            return this;
+        }
+
+        RingtoneSelection.Builder updated = new Builder(this);
+        // The relevant uris can't be null, because they contain userIdToStrip.
+        if (needToChangeSound) {
+            // mSoundUri is not null, so the result of getUriWithoutUserId won't be null.
+            updated.setSoundSource(ContentProvider.getUriWithoutUserId(mSoundUri));
+        }
+        if (needToChangeVibration) {
+            updated.setVibrationSource(ContentProvider.getUriWithoutUserId(mVibrationUri));
+        }
+        return updated.build();
+    }
+
+    @Override
+    public String toString() {
+        return toUri().toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (!(o instanceof RingtoneSelection other)) {
+            return false;
+        }
+        return this.mSoundSource == other.mSoundSource
+                && this.mVibrationSource == other.mVibrationSource
+                && Objects.equals(this.mSoundUri, other.mSoundUri)
+                && Objects.equals(this.mVibrationUri, other.mVibrationUri);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSoundSource, mVibrationSource, mSoundUri, mVibrationUri);
+    }
 
     /**
      * Converts a Uri into a RingtoneSelection.
      *
-     * <p>Null values and Uris that {@link #isRingtoneSelectionUri(Uri)} returns false will be
-     * treated according to the behaviour specified by the {@code unrecognizedValueBehavior}
-     * parameter.
+     * <p>Null values, Uris that {@link #isRingtoneSelectionUri(Uri)} returns false (except for
+     * old-style symbolic default Uris {@link RingtoneManager#getDefaultUri}) will be treated
+     * according to the behaviour specified by the {@code unrecognizedValueBehavior} parameter.
+     *
+     * <p>Symbolic default Uris (where {@link RingtoneManager#getDefaultType(Uri)} returns -1,
+     * will map sources to {@link #SOUND_SOURCE_SYSTEM_DEFAULT} and
+     * {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}.
      *
      * @param uri The Uri to convert, potentially null.
      * @param unrecognizedValueBehavior indicates how to treat values for which
@@ -384,21 +475,35 @@
         if (isRingtoneSelectionUri(uri)) {
             return parseRingtoneSelectionUri(uri);
         }
+        // Old symbolic default URIs map to the sources suggested by the unrecognized behavior.
+        // It doesn't always map to both sources because the app may have its own default behavior
+        // to apply, so non-primary sources are left as unspecified, which will revert to the
+        // system default in the absence of an app default.
+        boolean isDefaultUri = RingtoneManager.getDefaultType(uri) > 0;
         RingtoneSelection.Builder builder = new RingtoneSelection.Builder();
         switch (unrecognizedValueBehavior) {
             case FROM_URI_RINGTONE_SELECTION_ONLY:
-                // Always return use-defaults for unrecognized ringtone selection Uris.
+                if (isDefaultUri) {
+                    builder.setSoundSource(SOUND_SOURCE_SYSTEM_DEFAULT);
+                    builder.setVibrationSource(VIBRATION_SOURCE_SYSTEM_DEFAULT);
+                }
+                // Always return unspecified (defaults) for unrecognized ringtone selection Uris.
                 return builder.build();
             case FROM_URI_RINGTONE_SELECTION_OR_SOUND:
                 if (uri == null) {
                     return builder.setSoundSource(SOUND_SOURCE_OFF).build();
+                } else if (isDefaultUri) {
+                    return builder.setSoundSource(SOUND_SOURCE_SYSTEM_DEFAULT).build();
                 } else {
                     return builder.setSoundSource(uri).build();
                 }
             case FROM_URI_RINGTONE_SELECTION_OR_VIBRATION:
                 if (uri == null) {
                     return builder.setVibrationSource(VIBRATION_SOURCE_OFF).build();
+                } else if (isDefaultUri) {
+                    return builder.setVibrationSource(VIBRATION_SOURCE_SYSTEM_DEFAULT).build();
                 } else {
+                    // Unlike sound, there's no legacy settings alias uri for the default.
                     return builder.setVibrationSource(uri).build();
                 }
             default:
@@ -407,7 +512,12 @@
         }
     }
 
-    /** Parses the Uri, which has already been checked for {@link #isRingtoneSelectionUri(Uri)}. */
+    /**
+     * Parses the Uri, which has already been checked for {@link #isRingtoneSelectionUri(Uri)}.
+     * As a special case to be more compatible, if the RingtoneSelection has a userId specified in
+     * the authority, then this is pushed down into the sound and vibration Uri, as the selection
+     * itself is agnostic.
+     */
     @NonNull
     private static RingtoneSelection parseRingtoneSelectionUri(@NonNull Uri uri) {
         RingtoneSelection.Builder builder = new RingtoneSelection.Builder();
@@ -416,19 +526,39 @@
                 uri.getQueryParameter(URI_PARAM_VIBRATION_SOURCE));
         Uri soundUri = getParamAsUri(uri, URI_PARAM_SOUND_URI);
         Uri vibrationUri = getParamAsUri(uri, URI_PARAM_VIBRATION_URI);
+
+        // Add userId if necessary. This won't override an existing one in the sound/vib Uris.
+        int userIdFromAuthority = ContentProvider.getUserIdFromAuthority(
+                uri.getAuthority(), UserHandle.USER_NULL);
+        if (userIdFromAuthority != UserHandle.USER_NULL) {
+            // Won't override existing user id.
+            if (soundUri != null) {
+                soundUri = ContentProvider.maybeAddUserId(soundUri, userIdFromAuthority);
+            }
+            if (vibrationUri != null) {
+                vibrationUri = ContentProvider.maybeAddUserId(vibrationUri, userIdFromAuthority);
+            }
+        }
+
+        // Set sound uri if present, but map system default Uris to the system default source.
         if (soundUri != null) {
-            builder.setSoundSource(soundUri);
+            if (RingtoneManager.getDefaultType(uri) >= 0) {
+                builder.setSoundSource(SOUND_SOURCE_SYSTEM_DEFAULT);
+            } else {
+                builder.setSoundSource(soundUri);
+            }
         }
         if (vibrationUri != null) {
             builder.setVibrationSource(vibrationUri);
         }
+
         // Don't set the source if there's a URI and the source is default, because that will
         // override the Uri source set above. In effect, we prioritise "explicit" sources over
         // an implicit Uri source - except for "default", which isn't really explicit.
-        if (soundSource != SOUND_SOURCE_DEFAULT || soundUri == null) {
+        if (soundSource != SOUND_SOURCE_UNSPECIFIED || soundUri == null) {
             builder.setSoundSource(soundSource);
         }
-        if (vibrationSource != VIBRATION_SOURCE_DEFAULT || vibrationUri == null) {
+        if (vibrationSource != VIBRATION_SOURCE_UNSPECIFIED || vibrationUri == null) {
             builder.setVibrationSource(vibrationSource);
         }
         return builder.build();
@@ -450,26 +580,28 @@
      */
     @Nullable
     private static String soundSourceToString(@SoundSource int soundSource) {
-        switch (soundSource) {
-            case SOUND_SOURCE_OFF: return SOURCE_OFF_STRING;
-            default: return null;
-        }
+        return switch (soundSource) {
+            case SOUND_SOURCE_OFF -> SOURCE_OFF_STRING;
+            case SOUND_SOURCE_SYSTEM_DEFAULT -> SOURCE_SYSTEM_DEFAULT_STRING;
+            default -> null;
+        };
     }
 
     /**
      * Returns the sound source int corresponding to the query string value. Returns
      * {@link #SOUND_SOURCE_UNKNOWN} if the value isn't recognised, and
-     * {@link #SOUND_SOURCE_DEFAULT} if the value is {@code null} (not in the Uri).
+     * {@link #SOUND_SOURCE_UNSPECIFIED} if the value is {@code null} (not in the Uri).
      */
     @SoundSource
     private static int stringToSoundSource(@Nullable String soundSource) {
         if (soundSource == null) {
-            return SOUND_SOURCE_DEFAULT;
+            return SOUND_SOURCE_UNSPECIFIED;
         }
-        switch (soundSource) {
-            case SOURCE_OFF_STRING: return SOUND_SOURCE_OFF;
-            default: return SOUND_SOURCE_UNKNOWN;
-        }
+        return switch (soundSource) {
+            case SOURCE_OFF_STRING -> SOUND_SOURCE_OFF;
+            case SOURCE_SYSTEM_DEFAULT_STRING -> SOUND_SOURCE_SYSTEM_DEFAULT;
+            default -> SOUND_SOURCE_UNKNOWN;
+        };
     }
 
     /**
@@ -478,30 +610,31 @@
      */
     @Nullable
     private static String vibrationSourceToString(@VibrationSource int vibrationSource) {
-        switch (vibrationSource) {
-            case VIBRATION_SOURCE_OFF: return SOURCE_OFF_STRING;
-            case VIBRATION_SOURCE_AUDIO_CHANNEL: return VIBRATION_SOURCE_AUDIO_CHANNEL_STRING;
-            case VIBRATION_SOURCE_HAPTIC_GENERATOR:
-                return VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING;
-            case VIBRATION_SOURCE_APPLICATION_PROVIDED:
-                return VIBRATION_SOURCE_APPLICATION_PROVIDED_STRING;
-            default: return null;
-        }
+        return switch (vibrationSource) {
+            case VIBRATION_SOURCE_OFF -> SOURCE_OFF_STRING;
+            case VIBRATION_SOURCE_AUDIO_CHANNEL -> VIBRATION_SOURCE_AUDIO_CHANNEL_STRING;
+            case VIBRATION_SOURCE_HAPTIC_GENERATOR -> VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING;
+            case VIBRATION_SOURCE_APPLICATION_DEFAULT ->
+                    VIBRATION_SOURCE_APPLICATION_DEFAULT_STRING;
+            case VIBRATION_SOURCE_SYSTEM_DEFAULT -> SOURCE_SYSTEM_DEFAULT_STRING;
+            default -> null;
+        };
     }
 
     @VibrationSource
     private static int stringToVibrationSource(@Nullable String vibrationSource) {
         if (vibrationSource == null) {
-            return VIBRATION_SOURCE_DEFAULT;
+            return VIBRATION_SOURCE_UNSPECIFIED;
         }
-        switch (vibrationSource) {
-            case SOURCE_OFF_STRING: return VIBRATION_SOURCE_OFF;
-            case VIBRATION_SOURCE_AUDIO_CHANNEL_STRING: return VIBRATION_SOURCE_AUDIO_CHANNEL;
-            case VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING: return VIBRATION_SOURCE_HAPTIC_GENERATOR;
-            case VIBRATION_SOURCE_APPLICATION_PROVIDED_STRING:
-                return VIBRATION_SOURCE_APPLICATION_PROVIDED;
-            default: return VIBRATION_SOURCE_UNKNOWN;
-        }
+        return switch (vibrationSource) {
+            case SOURCE_OFF_STRING -> VIBRATION_SOURCE_OFF;
+            case SOURCE_SYSTEM_DEFAULT_STRING -> VIBRATION_SOURCE_SYSTEM_DEFAULT;
+            case VIBRATION_SOURCE_AUDIO_CHANNEL_STRING -> VIBRATION_SOURCE_AUDIO_CHANNEL;
+            case VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING -> VIBRATION_SOURCE_HAPTIC_GENERATOR;
+            case VIBRATION_SOURCE_APPLICATION_DEFAULT_STRING ->
+                    VIBRATION_SOURCE_APPLICATION_DEFAULT;
+            default -> VIBRATION_SOURCE_UNKNOWN;
+        };
     }
 
     /**
@@ -512,12 +645,13 @@
     public static final class Builder {
         private Uri mSoundUri;
         private Uri mVibrationUri;
-        @SoundSource private int mSoundSource = SOUND_SOURCE_DEFAULT;
-        @VibrationSource private int mVibrationSource = VIBRATION_SOURCE_DEFAULT;
+        @SoundSource private int mSoundSource = SOUND_SOURCE_UNSPECIFIED;
+        @VibrationSource private int mVibrationSource = VIBRATION_SOURCE_UNSPECIFIED;
 
         /**
          * Creates a new {@link RingtoneSelection} builder. A default ringtone selection has its
-         * sound and vibration source unset, which means they would fall back to system defaults.
+         * sound and vibration source unspecified, which means they would fall back to app/system
+         * defaults.
          */
         public Builder() {}
 
@@ -559,7 +693,9 @@
          */
         @NonNull
         public Builder setSoundSource(@NonNull Uri soundUri) {
-            mSoundUri = requireNonNull(soundUri);
+            // getCanonicalUri shouldn't return null. If it somehow did, then the
+            // RingtoneSelection constructor will revert this to unspecified.
+            mSoundUri = requireNonNull(soundUri).getCanonicalUri();
             mSoundSource = SOUND_SOURCE_URI;
             return this;
         }
@@ -587,7 +723,9 @@
          */
         @NonNull
         public Builder setVibrationSource(@NonNull Uri vibrationUri) {
-            mVibrationUri = requireNonNull(vibrationUri);
+            // getCanonicalUri shouldn't return null. If it somehow did, then the
+            // RingtoneSelection constructor will revert this to unspecified.
+            mVibrationUri = requireNonNull(vibrationUri).getCanonicalUri();
             mVibrationSource = VIBRATION_SOURCE_URI;
             return this;
         }
diff --git a/media/java/android/media/RingtoneV1.java b/media/java/android/media/RingtoneV1.java
index 3c54d4a..b761afa 100644
--- a/media/java/android/media/RingtoneV1.java
+++ b/media/java/android/media/RingtoneV1.java
@@ -16,15 +16,14 @@
 
 package android.media;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources.NotFoundException;
 import android.media.audiofx.HapticGenerator;
 import android.net.Uri;
 import android.os.Binder;
-import android.os.Build;
 import android.os.RemoteException;
 import android.os.Trace;
 import android.os.VibrationEffect;
@@ -62,6 +61,7 @@
 
     private final Context mContext;
     private final AudioManager mAudioManager;
+    private final Ringtone.Injectables mInjectables;
     private VolumeShaper.Configuration mVolumeShaperConfig;
     private VolumeShaper mVolumeShaper;
 
@@ -74,12 +74,10 @@
     private final IRingtonePlayer mRemotePlayer;
     private final Binder mRemoteToken;
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private MediaPlayer mLocalPlayer;
     private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener();
     private HapticGenerator mHapticGenerator;
 
-    @UnsupportedAppUsage
     private Uri mUri;
     private String mTitle;
 
@@ -94,10 +92,15 @@
     private boolean mHapticGeneratorEnabled = false;
     private final Object mPlaybackSettingsLock = new Object();
 
-    /** {@hide} */
-    @UnsupportedAppUsage
+    /** @hide */
     public RingtoneV1(Context context, boolean allowRemote) {
+        this(context, new Ringtone.Injectables(), allowRemote);
+    }
+
+    /** @hide */
+    RingtoneV1(Context context, @NonNull Ringtone.Injectables injectables, boolean allowRemote) {
         mContext = context;
+        mInjectables = injectables;
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         mAllowRemote = allowRemote;
         mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null;
@@ -200,7 +203,7 @@
         }
         destroyLocalPlayer();
         // try opening uri locally before delegating to remote player
-        mLocalPlayer = new MediaPlayer();
+        mLocalPlayer = mInjectables.newMediaPlayer();
         try {
             mLocalPlayer.setDataSource(mContext, mUri);
             mLocalPlayer.setAudioAttributes(mAudioAttributes);
@@ -240,19 +243,7 @@
      */
     public boolean hasHapticChannels() {
         // FIXME: support remote player, or internalize haptic channels support and remove entirely.
-        try {
-            android.os.Trace.beginSection("Ringtone.hasHapticChannels");
-            if (mLocalPlayer != null) {
-                for(MediaPlayer.TrackInfo trackInfo : mLocalPlayer.getTrackInfo()) {
-                    if (trackInfo.hasHapticChannels()) {
-                        return true;
-                    }
-                }
-            }
-        } finally {
-            android.os.Trace.endSection();
-        }
-        return false;
+        return mInjectables.hasHapticChannels(mLocalPlayer);
     }
 
     /**
@@ -334,7 +325,7 @@
      * @see android.media.audiofx.HapticGenerator#isAvailable()
      */
     public boolean setHapticGeneratorEnabled(boolean enabled) {
-        if (!HapticGenerator.isAvailable()) {
+        if (!mInjectables.isHapticGeneratorAvailable()) {
             return false;
         }
         synchronized (mPlaybackSettingsLock) {
@@ -362,7 +353,7 @@
             mLocalPlayer.setVolume(mVolume);
             mLocalPlayer.setLooping(mIsLooping);
             if (mHapticGenerator == null && mHapticGeneratorEnabled) {
-                mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId());
+                mHapticGenerator = mInjectables.createHapticGenerator(mLocalPlayer);
             }
             if (mHapticGenerator != null) {
                 mHapticGenerator.setEnabled(mHapticGeneratorEnabled);
@@ -397,7 +388,6 @@
      *
      * @hide
      */
-    @UnsupportedAppUsage
     public void setUri(Uri uri) {
         setUri(uri, null);
     }
@@ -425,7 +415,6 @@
     }
 
     /** {@hide} */
-    @UnsupportedAppUsage
     public Uri getUri() {
         return mUri;
     }
@@ -556,7 +545,7 @@
                 Log.e(TAG, "Could not load fallback ringtone");
                 return false;
             }
-            mLocalPlayer = new MediaPlayer();
+            mLocalPlayer = mInjectables.newMediaPlayer();
             if (afd.getDeclaredLength() < 0) {
                 mLocalPlayer.setDataSource(afd.getFileDescriptor());
             } else {
@@ -594,12 +583,12 @@
     }
 
     public boolean isLocalOnly() {
-        return mAllowRemote;
+        return !mAllowRemote;
     }
 
     public boolean isUsingRemotePlayer() {
         // V2 testing api, but this is the v1 approximation.
-        return (mLocalPlayer == null) && mAllowRemote && (mRemotePlayer != null);
+        return (mLocalPlayer == null) && mAllowRemote && (mRemotePlayer != null) && (mUri != null);
     }
 
     class MyOnCompletionListener implements MediaPlayer.OnCompletionListener {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 83d2b61..c9cfa67 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -15,8 +15,22 @@
 }
 
 flag {
-     namespace: "media_solutions"
-     name: "enable_audio_policies_device_and_bluetooth_controller"
-     description: "Use Audio Policies implementation for device and Bluetooth route controllers."
-     bug: "280576228"
+    namespace: "media_solutions"
+    name: "enable_audio_policies_device_and_bluetooth_controller"
+    description: "Use Audio Policies implementation for device and Bluetooth route controllers."
+    bug: "280576228"
+}
+
+flag {
+    namespace: "media_solutions"
+    name: "disable_screen_off_broadcast_receiver"
+    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/SectionRequest.java b/media/java/android/media/tv/SectionRequest.java
index 078e832..ec0d7f7 100644
--- a/media/java/android/media/tv/SectionRequest.java
+++ b/media/java/android/media/tv/SectionRequest.java
@@ -81,7 +81,7 @@
     /**
      * Gets the version number of requested session. If it is null, value will be -1.
      * <p>The consistency of version numbers between request and response depends on
-     * {@link BroadcastInfoRequest.RequestOption}. If the request has RequestOption value
+     * {@link BroadcastInfoRequest#getOption()}. If the request has RequestOption value
      * REQUEST_OPTION_AUTO_UPDATE, then the response may be set to the latest version which may be
      * different from the version of the request. Otherwise, response with a different version from
      * its request will be considered invalid.
diff --git a/media/java/android/media/tv/SectionResponse.java b/media/java/android/media/tv/SectionResponse.java
index f38ea9d..10333fe 100644
--- a/media/java/android/media/tv/SectionResponse.java
+++ b/media/java/android/media/tv/SectionResponse.java
@@ -76,7 +76,7 @@
     /**
      * Gets the Version number of requested session. If it is null, value will be -1.
      * <p>The consistency of version numbers between request and response depends on
-     * {@link BroadcastInfoRequest.RequestOption}. If the request has RequestOption value
+     * {@link BroadcastInfoRequest#getOption()}. If the request has RequestOption value
      * REQUEST_OPTION_AUTO_UPDATE, then the response may be set to the latest version which may be
      * different from the version of the request. Otherwise, response with a different version from
      * its request will be considered invalid.
diff --git a/media/java/android/media/tv/TableRequest.java b/media/java/android/media/tv/TableRequest.java
index d9587f6..06df07f 100644
--- a/media/java/android/media/tv/TableRequest.java
+++ b/media/java/android/media/tv/TableRequest.java
@@ -129,7 +129,7 @@
     /**
      * Gets the version number of requested table. If it is null, value will be -1.
      * <p>The consistency of version numbers between request and response depends on
-     * {@link BroadcastInfoRequest.RequestOption}. If the request has RequestOption value
+     * {@link BroadcastInfoRequest#getOption()}. If the request has RequestOption value
      * REQUEST_OPTION_AUTO_UPDATE, then the response may be set to the latest version which may be
      * different from the version of the request. Otherwise, response with a different version from
      * its request will be considered invalid.
diff --git a/media/java/android/media/tv/TableResponse.java b/media/java/android/media/tv/TableResponse.java
index c4fc26e..1daf452 100644
--- a/media/java/android/media/tv/TableResponse.java
+++ b/media/java/android/media/tv/TableResponse.java
@@ -269,7 +269,7 @@
     /**
      * Gets the version number of requested table. If it is null, value will be -1.
      * <p>The consistency of version numbers between request and response depends on
-     * {@link BroadcastInfoRequest.RequestOption}. If the request has RequestOption value
+     * {@link BroadcastInfoRequest#getOption()}. If the request has RequestOption value
      * REQUEST_OPTION_AUTO_UPDATE, then the response may be set to the latest version which may be
      * different from the version of the request. Otherwise, response with a different version from
      * its request will be considered invalid.
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 667a9ae..2db4be8 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -939,9 +939,8 @@
                 type = TYPE_HDMI;
                 isHardwareInput = true;
                 hdmiConnectionRelativePosition = getRelativePosition(mContext, mHdmiDeviceInfo);
-                isConnectedToHdmiSwitch =
-                        hdmiConnectionRelativePosition
-                                != HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW;
+                isConnectedToHdmiSwitch = hdmiConnectionRelativePosition
+                                == HdmiUtils.HDMI_RELATIVE_POSITION_BELOW;
             } else if (mTvInputHardwareInfo != null) {
                 id = generateInputId(componentName, mTvInputHardwareInfo);
                 type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER);
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index c616b84f..1c25080 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -38,6 +38,8 @@
 #include <mediadrm/IDrmMetricsConsumer.h>
 #include <mediadrm/IDrm.h>
 #include <utils/Vector.h>
+#include <map>
+#include <string>
 
 using ::android::os::PersistableBundle;
 namespace drm = ::android::hardware::drm;
@@ -193,6 +195,11 @@
     jclass classId;
 };
 
+struct DrmExceptionFields {
+    jmethodID init;
+    jclass classId;
+};
+
 struct fields_t {
     jfieldID context;
     jmethodID post_event;
@@ -215,6 +222,7 @@
     jclass parcelCreatorClassId;
     KeyStatusFields keyStatus;
     LogMessageFields logMessage;
+    std::map<std::string, DrmExceptionFields> exceptionCtors;
 };
 
 static fields_t gFields;
@@ -245,18 +253,32 @@
     return arrayList;
 }
 
-int drmThrowException(JNIEnv* env, const char *className, const DrmStatus &err, const char *msg) {
+void resolveDrmExceptionCtor(JNIEnv *env, const char *className) {
+    jclass clazz;
+    jmethodID init;
+    FIND_CLASS(clazz, className);
+    GET_METHOD_ID(init, clazz, "<init>", "(Ljava/lang/String;III)V");
+    gFields.exceptionCtors[std::string(className)] = {
+        .init = init,
+        .classId = static_cast<jclass>(env->NewGlobalRef(clazz))
+        };
+}
+
+void drmThrowException(JNIEnv* env, const char *className, const DrmStatus &err, const char *msg) {
     using namespace android::jnihelp;
-    jstring _detailMessage = CreateExceptionMsg(env, msg);
-    int _status = ThrowException(env, className, "(Ljava/lang/String;III)V",
-                                 _detailMessage,
-                                 err.getCdmErr(),
-                                 err.getOemErr(),
-                                 err.getContext());
-    if (_detailMessage != NULL) {
-        env->DeleteLocalRef(_detailMessage);
+
+    if (gFields.exceptionCtors.count(std::string(className)) == 0) {
+        jniThrowException(env, className, msg);
+    } else {
+        jstring _detailMessage = CreateExceptionMsg(env, msg);
+        jobject exception = env->NewObject(gFields.exceptionCtors[std::string(className)].classId,
+            gFields.exceptionCtors[std::string(className)].init, _detailMessage,
+            err.getCdmErr(), err.getOemErr(), err.getContext());
+        env->Throw(static_cast<jthrowable>(exception));
+        if (_detailMessage != NULL) {
+            env->DeleteLocalRef(_detailMessage);
+        }
     }
-    return _status;
 }
 }  // namespace anonymous
 
@@ -952,6 +974,10 @@
     FIND_CLASS(clazz, "android/media/MediaDrm$LogMessage");
     gFields.logMessage.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
     GET_METHOD_ID(gFields.logMessage.init, clazz, "<init>", "(JILjava/lang/String;)V");
+
+    resolveDrmExceptionCtor(env, "android/media/NotProvisionedException");
+    resolveDrmExceptionCtor(env, "android/media/ResourceBusyException");
+    resolveDrmExceptionCtor(env, "android/media/DeniedByServerException");
 }
 
 static void android_media_MediaDrm_native_setup(
@@ -2192,4 +2218,4 @@
 int register_android_media_Drm(JNIEnv *env) {
     return AndroidRuntime::registerNativeMethods(env,
                 "android/media/MediaDrm", gMethods, NELEM(gMethods));
-}
+}
\ No newline at end of file
diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml
index e886558..7d79a6c 100644
--- a/media/tests/MediaFrameworkTest/AndroidManifest.xml
+++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml
@@ -20,6 +20,7 @@
     <uses-permission android:name="android.permission.RECORD_AUDIO"/>
     <uses-permission android:name="android.permission.CAMERA"/>
     <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
     <uses-permission android:name="android.permission.WAKE_LOCK"/>
diff --git a/media/tests/MediaFrameworkTest/AndroidTest.xml b/media/tests/MediaFrameworkTest/AndroidTest.xml
index 132028c..91c92cc1 100644
--- a/media/tests/MediaFrameworkTest/AndroidTest.xml
+++ b/media/tests/MediaFrameworkTest/AndroidTest.xml
@@ -23,5 +23,6 @@
         <option name="package" value="com.android.mediaframeworktest" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="hidden-api-checks" value="false"/>
+        <option name="isolated-storage" value="false"/>
     </test>
 </configuration>
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
index 9be7004..30edfa4 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
@@ -44,7 +44,6 @@
     @Override
     public TestSuite getAllTests() {
         TestSuite suite = new InstrumentationTestSuite(this);
-        addMediaMetadataRetrieverStateUnitTests(suite);
         addMediaRecorderStateUnitTests(suite);
         addMediaPlayerStateUnitTests(suite);
         addMediaScannerUnitTests(suite);
@@ -70,11 +69,6 @@
     }
 
     // Running all unit tests checking the state machine may be time-consuming.
-    private void addMediaMetadataRetrieverStateUnitTests(TestSuite suite) {
-        suite.addTestSuite(MediaMetadataRetrieverTest.class);
-    }
-
-    // Running all unit tests checking the state machine may be time-consuming.
     private void addMediaRecorderStateUnitTests(TestSuite suite) {
         suite.addTestSuite(MediaRecorderPrepareStateUnitTest.class);
         suite.addTestSuite(MediaRecorderResetStateUnitTest.class);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
index bdca474..f70d2d1 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
@@ -16,26 +16,34 @@
 
 package com.android.mediaframeworktest.unit;
 
+import static org.junit.Assert.assertTrue;
+
 import android.graphics.Bitmap;
 import android.media.MediaMetadataRetriever;
-import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.mediaframeworktest.MediaNames;
 import com.android.mediaframeworktest.MediaProfileReader;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.FileOutputStream;
 import java.io.IOException;
 
-public class MediaMetadataRetrieverTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class MediaMetadataRetrieverTest {
 
     private static final String TAG = "MediaMetadataRetrieverTest";
 
     // Test album art extraction.
     @MediumTest
-    public static void testGetEmbeddedPicture() throws Exception {
+    @Test
+    public void testGetEmbeddedPicture() throws Exception {
         Log.v(TAG, "testGetEmbeddedPicture starts.");
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         boolean supportWMA = MediaProfileReader.getWMAEnable();
@@ -78,7 +86,8 @@
 
     // Test frame capture
     @LargeTest
-    public static void testThumbnailCapture() throws Exception {
+    @Test
+    public void testThumbnailCapture() throws Exception {
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         boolean supportWMA = MediaProfileReader.getWMAEnable();
         boolean supportWMV = MediaProfileReader.getWMVEnable();
@@ -134,7 +143,8 @@
     }
 
     @LargeTest
-    public static void testMetadataRetrieval() throws Exception {
+    @Test
+    public void testMetadataRetrieval() throws Exception {
         boolean supportWMA = MediaProfileReader.getWMAEnable();
         boolean supportWMV = MediaProfileReader.getWMVEnable();
         boolean hasFailed = false;
@@ -169,7 +179,8 @@
     // If the specified call order and valid media file is used, no exception
     // should be thrown.
     @MediumTest
-    public static void testBasicNormalMethodCallSequence() throws Exception {
+    @Test
+    public void testBasicNormalMethodCallSequence() throws Exception {
         boolean hasFailed = false;
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         try {
@@ -197,7 +208,8 @@
     // If setDataSource() has not been called, both getFrameAtTime() and extractMetadata() must
     // return null.
     @MediumTest
-    public static void testBasicAbnormalMethodCallSequence() {
+    @Test
+    public void testBasicAbnormalMethodCallSequence() {
         boolean hasFailed = false;
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         if (retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM) != null) {
@@ -213,7 +225,8 @@
 
     // Test setDataSource()
     @MediumTest
-    public static void testSetDataSource() throws IOException {
+    @Test
+    public void testSetDataSource() throws IOException {
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         boolean hasFailed = false;
 
diff --git a/media/tests/ringtone/Android.bp b/media/tests/ringtone/Android.bp
index 55b98c4..8d1e5e3 100644
--- a/media/tests/ringtone/Android.bp
+++ b/media/tests/ringtone/Android.bp
@@ -9,15 +9,24 @@
     srcs: ["src/**/*.java"],
 
     libs: [
-        "android.test.runner",
         "android.test.base",
+        "android.test.mock",
+        "android.test.runner",
     ],
 
     static_libs: [
-        "androidx.test.rules",
-        "testng",
+        "androidx.test.ext.junit",
         "androidx.test.ext.truth",
+        "androidx.test.rules",
         "frameworks-base-testutils",
+        "mockito-target-inline-minus-junit4",
+        "testables",
+        "testng",
+    ],
+
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
     ],
 
     test_suites: [
diff --git a/media/tests/ringtone/OWNERS b/media/tests/ringtone/OWNERS
new file mode 100644
index 0000000..93b44f4
--- /dev/null
+++ b/media/tests/ringtone/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 345036
+
+include /services/core/java/com/android/server/vibrator/OWNERS
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java b/media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java
similarity index 69%
rename from media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
rename to media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java
index 3c0c684..2c8daba 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
+++ b/media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java
@@ -14,20 +14,22 @@
  * limitations under the License.
  */
 
-package com.android.mediaframeworktest.unit;
+package com.android.media;
 
 import static android.media.Ringtone.MEDIA_SOUND;
 import static android.media.Ringtone.MEDIA_SOUND_AND_VIBRATION;
 import static android.media.Ringtone.MEDIA_VIBRATION;
 
+import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerFallbackSetup;
+import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerSetup;
+import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerStarted;
+import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerStopped;
+
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.never;
@@ -53,34 +55,29 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.testing.TestableContext;
-import android.util.ArrayMap;
-import android.util.ArraySet;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.mediaframeworktest.R;
+import com.android.framework.base.media.ringtone.tests.R;
+import com.android.media.testing.RingtoneInjectablesTrackingTestRule;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
 import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.io.FileNotFoundException;
-import java.util.ArrayDeque;
-import java.util.Map;
-import java.util.Queue;
 
+/**
+ * Test behavior of {@link Ringtone} when it's created via {@link Ringtone.Builder}.
+ */
 @RunWith(AndroidJUnit4.class)
-public class RingtoneTest {
+public class RingtoneBuilderTest {
 
     private static final Uri SOUND_URI = Uri.parse("content://fake-sound-uri");
 
@@ -93,11 +90,8 @@
 
     private static final VibrationEffect VIBRATION_EFFECT =
             VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100}, -1);
-    private static final VibrationEffect VIBRATION_EFFECT_REPEATING =
-            VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100, 50}, 1);
 
-    @Rule
-    public final RingtoneInjectablesTrackingTestRule
+    @Rule public final RingtoneInjectablesTrackingTestRule
             mMediaPlayerRule = new RingtoneInjectablesTrackingTestRule();
 
     @Captor private ArgumentCaptor<IBinder> mIBinderCaptor;
@@ -122,6 +116,7 @@
         mContext = spy(testContext);
     }
 
+
     @Test
     public void testRingtone_fullLifecycleUsingLocalMediaPlayer() throws Exception {
         MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
@@ -142,14 +137,14 @@
         assertThat(ringtone.isLocalOnly()).isFalse();
 
         // Prepare
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES);
+        verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES);
         verify(mockMediaPlayer).setVolume(1.0f);
         verify(mockMediaPlayer).setLooping(false);
         verify(mockMediaPlayer).prepare();
 
         // Play
         ringtone.play();
-        verifyLocalPlay(mockMediaPlayer);
+        verifyPlayerStarted(mockMediaPlayer);
 
         // Verify dynamic controls.
         ringtone.setVolume(0.8f);
@@ -165,7 +160,7 @@
 
         // Release
         ringtone.stop();
-        verifyLocalStop(mockMediaPlayer);
+        verifyPlayerStopped(mockMediaPlayer);
 
         // This test is intended to strictly verify all interactions with MediaPlayer in a local
         // playback case. This shouldn't be necessary in other tests that have the same basic
@@ -199,16 +194,16 @@
         assertThat(ringtone.getAudioAttributes()).isEqualTo(audioAttributes);
 
         // Prepare
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, audioAttributes);
+        verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, audioAttributes);
         verify(mockMediaPlayer).prepare();
 
         // Play
         ringtone.play();
-        verifyLocalPlay(mockMediaPlayer);
+        verifyPlayerStarted(mockMediaPlayer);
 
         // Release
         ringtone.stop();
-        verifyLocalStop(mockMediaPlayer);
+        verifyPlayerStopped(mockMediaPlayer);
 
         verifyZeroInteractions(mMockRemotePlayer);
         verifyZeroInteractions(mMockVibrator);
@@ -220,8 +215,8 @@
         setupFileNotFound(mockMediaPlayer, SOUND_URI);
         Ringtone ringtone =
                 newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
-                .setUri(SOUND_URI)
-                .build();
+                        .setUri(SOUND_URI)
+                        .build();
         assertThat(ringtone).isNotNull();
         assertThat(ringtone.isUsingRemotePlayer()).isTrue();
 
@@ -284,7 +279,7 @@
         // Prepare
         // Uses attributes with haptic channels enabled, but will use the effect when there aren't
         // any present.
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+        verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
         verify(mockMediaPlayer).setVolume(1.0f);
         verify(mockMediaPlayer).setLooping(false);
         verify(mockMediaPlayer).prepare();
@@ -292,7 +287,7 @@
         // Play
         ringtone.play();
 
-        verifyLocalPlay(mockMediaPlayer);
+        verifyPlayerStarted(mockMediaPlayer);
         verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
 
         // Verify dynamic controls.
@@ -310,7 +305,7 @@
 
         // Release
         ringtone.stop();
-        verifyLocalStop(mockMediaPlayer);
+        verifyPlayerStopped(mockMediaPlayer);
         verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
 
         // This test is intended to strictly verify all interactions with MediaPlayer in a local
@@ -388,7 +383,7 @@
         // Prepare
         // Uses attributes with haptic channels enabled, but will abandon the MediaPlayer when it
         // knows there aren't any.
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+        verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
         verify(mockMediaPlayer).setVolume(0.0f);  // Vibration-only: sound muted.
         verify(mockMediaPlayer).setLooping(false);
         verify(mockMediaPlayer).prepare();
@@ -443,7 +438,7 @@
         // Prepare
         // Uses attributes with haptic channels enabled, but will use the effect when there aren't
         // any present.
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+        verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
         verify(mockMediaPlayer).setVolume(0.0f);  // Vibration-only: sound muted.
         verify(mockMediaPlayer).setLooping(false);
         verify(mockMediaPlayer).prepare();
@@ -451,7 +446,7 @@
         // Play
         ringtone.play();
         // Vibrator.vibrate isn't called because the vibration comes from the sound.
-        verifyLocalPlay(mockMediaPlayer);
+        verifyPlayerStarted(mockMediaPlayer);
 
         // Verify dynamic controls (no-op without sound)
         ringtone.setVolume(0.8f);
@@ -466,7 +461,7 @@
 
         // Release
         ringtone.stop();
-        verifyLocalStop(mockMediaPlayer);
+        verifyPlayerStopped(mockMediaPlayer);
 
         // This test is intended to strictly verify all interactions with MediaPlayer in a local
         // playback case. This shouldn't be necessary in other tests that have the same basic
@@ -496,17 +491,17 @@
 
         // Prepare
         // The attributes here have haptic channels enabled (unlike above)
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+        verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
         verify(mockMediaPlayer).prepare();
 
         // Play
         ringtone.play();
         when(mockMediaPlayer.isPlaying()).thenReturn(true);
-        verifyLocalPlay(mockMediaPlayer);
+        verifyPlayerStarted(mockMediaPlayer);
 
         // Release
         ringtone.stop();
-        verifyLocalStop(mockMediaPlayer);
+        verifyPlayerStopped(mockMediaPlayer);
 
         verifyZeroInteractions(mMockRemotePlayer);
         // Nothing after the initial hasVibrator - it uses audio-coupled.
@@ -536,7 +531,7 @@
 
         // Prepare
         // The attributes here have haptic channels enabled (unlike above)
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+        verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
         verify(mockMediaPlayer).prepare();
 
         // Play
@@ -559,7 +554,7 @@
     @Test
     public void testRingtone_nullMediaOnBuilderUsesFallback() throws Exception {
         AssetFileDescriptor testResourceFd =
-                mContext.getResources().openRawResourceFd(R.raw.shortmp3);
+                mContext.getResources().openRawResourceFd(R.raw.test_sound_file);
         // Ensure it will flow as expected.
         assertThat(testResourceFd).isNotNull();
         assertThat(testResourceFd.getDeclaredLength()).isAtLeast(0);
@@ -575,18 +570,18 @@
 
         // Delegates straight to fallback in local player.
         // Prepare
-        verifyLocalPlayerFallbackSetup(mockMediaPlayer, testResourceFd, RINGTONE_ATTRIBUTES);
+        verifyPlayerFallbackSetup(mockMediaPlayer, testResourceFd, RINGTONE_ATTRIBUTES);
         verify(mockMediaPlayer).setVolume(1.0f);
         verify(mockMediaPlayer).setLooping(false);
         verify(mockMediaPlayer).prepare();
 
         // Play
         ringtone.play();
-        verifyLocalPlay(mockMediaPlayer);
+        verifyPlayerStarted(mockMediaPlayer);
 
         // Release
         ringtone.stop();
-        verifyLocalStop(mockMediaPlayer);
+        verifyPlayerStopped(mockMediaPlayer);
 
         verifyNoMoreInteractions(mockMediaPlayer);
         verifyNoMoreInteractions(mMockRemotePlayer);
@@ -615,24 +610,10 @@
         verifyNoMoreInteractions(mMockRemotePlayer);
     }
 
-    @Test
-    public void testRingtone_noMediaSetOnBuilderFallbackFailsAndNoRemote() throws Exception {
-        mContext.getOrCreateTestableResources()
-                .addOverride(com.android.internal.R.raw.fallbackring, null);
-        Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
-                .setUri(null)
-                .setLocalOnly()
-                .build();
-        // Local player fallback fails as the resource isn't found (no media player creation is
-        // attempted), and since there is no local player, the ringtone ends up having nothing to
-        // do.
-        assertThat(ringtone).isNull();
-    }
-
     private Ringtone.Builder newBuilder(@Ringtone.RingtoneMedia int ringtoneMedia,
             AudioAttributes audioAttributes) {
         return new Ringtone.Builder(mContext, ringtoneMedia, audioAttributes)
-                .setInjectables(mMediaPlayerRule.injectables);
+                .setInjectables(mMediaPlayerRule.getRingtoneTestInjectables());
     }
 
     private static AudioAttributes audioAttributes(int audioUsage) {
@@ -647,194 +628,4 @@
         doThrow(new FileNotFoundException("Fake file not found"))
                 .when(mockMediaPlayer).setDataSource(any(Context.class), eq(uri));
     }
-
-    private void verifyLocalPlayerSetup(MediaPlayer mockPlayer, Uri expectedUri,
-            AudioAttributes expectedAudioAttributes) throws Exception {
-        verify(mockPlayer).setDataSource(mContext, expectedUri);
-        verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
-        verify(mockPlayer).setPreferredDevice(null);
-        verify(mockPlayer).prepare();
-    }
-
-    private void verifyLocalPlayerFallbackSetup(MediaPlayer mockPlayer, AssetFileDescriptor afd,
-            AudioAttributes expectedAudioAttributes) throws Exception {
-        // This is very specific but it's a simple way to test that the test resource matches.
-        if (afd.getDeclaredLength() < 0) {
-            verify(mockPlayer).setDataSource(afd.getFileDescriptor());
-        } else {
-            verify(mockPlayer).setDataSource(afd.getFileDescriptor(),
-                    afd.getStartOffset(),
-                    afd.getDeclaredLength());
-        }
-        verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
-        verify(mockPlayer).setPreferredDevice(null);
-        verify(mockPlayer).prepare();
-    }
-
-    private void verifyLocalPlay(MediaPlayer mockMediaPlayer) {
-        verify(mockMediaPlayer).setOnCompletionListener(any());
-        verify(mockMediaPlayer).start();
-    }
-
-    private void verifyLocalStop(MediaPlayer mockMediaPlayer) {
-        verify(mockMediaPlayer).stop();
-        verify(mockMediaPlayer).setOnCompletionListener(isNull());
-        verify(mockMediaPlayer).reset();
-        verify(mockMediaPlayer).release();
-    }
-
-    /**
-     * This rule ensures that all expected media player creations from the factory do actually
-     * occur. The reason for this level of control is that creating a media player is fairly
-     * expensive and blocking, so we do want unit tests of this class to "declare" interactions
-     * of all created media players.
-     *
-     * This needs to be a TestRule so that the teardown assertions can be skipped if the test has
-     * failed (and media player assertions may just be a distracting side effect). Otherwise, the
-     * teardown failures hide the real test ones.
-     */
-    public static class RingtoneInjectablesTrackingTestRule implements TestRule {
-        public Ringtone.Injectables injectables = new TestInjectables();
-        public boolean hapticGeneratorAvailable = true;
-
-        // Queue of (local) media players, in order of expected creation. Enqueue using
-        // expectNewMediaPlayer(), dequeued by the media player factory passed to Ringtone.
-        // This queue is asserted to be empty at the end of the test.
-        private Queue<MediaPlayer> mMockMediaPlayerQueue = new ArrayDeque<>();
-
-        // Similar to media players, but for haptic generator, which also needs releasing.
-        private Map<MediaPlayer, HapticGenerator> mMockHapticGeneratorMap = new ArrayMap<>();
-
-        // Media players with haptic channels.
-        private ArraySet<MediaPlayer> mHapticChannels = new ArraySet<>();
-
-        @Override
-        public Statement apply(Statement base, Description description) {
-            return new Statement() {
-                @Override
-                public void evaluate() throws Throwable {
-                    base.evaluate();
-                    // Only assert if the test didn't fail (base.evaluate() would throw).
-                    assertWithMessage("Test setup an expectLocalMediaPlayer but it wasn't consumed")
-                            .that(mMockMediaPlayerQueue).isEmpty();
-                    // Only assert if the test didn't fail (base.evaluate() would throw).
-                    assertWithMessage(
-                            "Test setup an expectLocalHapticGenerator but it wasn't consumed")
-                            .that(mMockHapticGeneratorMap).isEmpty();
-                }
-            };
-        }
-
-        private TestMediaPlayer expectLocalMediaPlayer() {
-            TestMediaPlayer mockMediaPlayer = Mockito.mock(TestMediaPlayer.class);
-            // Delegate to simulated methods. This means they can be verified but also reflect
-            // realistic transitions from the TestMediaPlayer.
-            doCallRealMethod().when(mockMediaPlayer).start();
-            doCallRealMethod().when(mockMediaPlayer).stop();
-            doCallRealMethod().when(mockMediaPlayer).setLooping(anyBoolean());
-            when(mockMediaPlayer.isLooping()).thenCallRealMethod();
-            when(mockMediaPlayer.isLooping()).thenCallRealMethod();
-            mMockMediaPlayerQueue.add(mockMediaPlayer);
-            return mockMediaPlayer;
-        }
-
-        private HapticGenerator expectHapticGenerator(MediaPlayer mockMediaPlayer) {
-            HapticGenerator mockHapticGenerator = Mockito.mock(HapticGenerator.class);
-            // A test should never want this.
-            assertWithMessage("Can't expect a second haptic generator created "
-                    + "for one media player")
-                    .that(mMockHapticGeneratorMap.put(mockMediaPlayer, mockHapticGenerator))
-                    .isNull();
-            return mockHapticGenerator;
-        }
-
-        private void setHasHapticChannels(MediaPlayer mp, boolean hasHapticChannels) {
-            if (hasHapticChannels) {
-                mHapticChannels.add(mp);
-            } else {
-                mHapticChannels.remove(mp);
-            }
-        }
-
-        private class TestInjectables extends Ringtone.Injectables {
-            @Override
-            public MediaPlayer newMediaPlayer() {
-                assertWithMessage(
-                        "Unexpected MediaPlayer creation. Bug or need expectNewMediaPlayer")
-                        .that(mMockMediaPlayerQueue)
-                        .isNotEmpty();
-                return mMockMediaPlayerQueue.remove();
-            }
-
-            @Override
-            public boolean isHapticGeneratorAvailable() {
-                return hapticGeneratorAvailable;
-            }
-
-            @Override
-            public HapticGenerator createHapticGenerator(MediaPlayer mediaPlayer) {
-                HapticGenerator mockHapticGenerator = mMockHapticGeneratorMap.remove(mediaPlayer);
-                assertWithMessage("Unexpected HapticGenerator creation. "
-                        + "Bug or need expectHapticGenerator")
-                        .that(mockHapticGenerator)
-                        .isNotNull();
-                return mockHapticGenerator;
-            }
-
-            @Override
-            public boolean isHapticPlaybackSupported() {
-                return true;
-            }
-
-            @Override
-            public boolean hasHapticChannels(MediaPlayer mp) {
-                return mHapticChannels.contains(mp);
-            }
-        }
-    }
-
-    /**
-     * MediaPlayer relies on a native backend and so its necessary to intercept calls from
-     * fake usage hitting them.
-     *
-     * Mocks don't work directly on native calls, but if they're overridden then it does work.
-     * Some basic state faking is also done to make the mocks more realistic.
-     */
-    private static class TestMediaPlayer extends MediaPlayer {
-        private boolean mIsPlaying = false;
-        private boolean mIsLooping = false;
-
-        @Override
-        public void start() {
-            mIsPlaying = true;
-        }
-
-        @Override
-        public void stop() {
-            mIsPlaying = false;
-        }
-
-        @Override
-        public void setLooping(boolean value) {
-            mIsLooping = value;
-        }
-
-        @Override
-        public boolean isLooping() {
-            return mIsLooping;
-        }
-
-        @Override
-        public boolean isPlaying() {
-            return mIsPlaying;
-        }
-
-        void simulatePlayingFinished() {
-            if (!mIsPlaying) {
-                throw new IllegalStateException(
-                        "Attempted to pretend playing finished when not playing");
-            }
-            mIsPlaying = false;
-        }
-    }
 }
diff --git a/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java b/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java
new file mode 100644
index 0000000..e97e117
--- /dev/null
+++ b/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media.testing;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioAttributes;
+import android.media.MediaPlayer;
+import android.net.Uri;
+
+/**
+ * Helper class with assertion methods on mock {@link MediaPlayer} instances.
+ */
+public final class MediaPlayerTestHelper {
+
+    /** Verify this local media player mock instance was started. */
+    public static void verifyPlayerStarted(MediaPlayer mockMediaPlayer) {
+        verify(mockMediaPlayer).setOnCompletionListener(any());
+        verify(mockMediaPlayer).start();
+    }
+
+    /** Verify this local media player mock instance was stopped and released. */
+    public static void verifyPlayerStopped(MediaPlayer mockMediaPlayer) {
+        verify(mockMediaPlayer).stop();
+        verify(mockMediaPlayer).setOnCompletionListener(isNull());
+        verify(mockMediaPlayer).reset();
+        verify(mockMediaPlayer).release();
+    }
+
+    /** Verify this local media player mock instance was setup with given attributes. */
+    public static void verifyPlayerSetup(Context context, MediaPlayer mockPlayer,
+            Uri expectedUri, AudioAttributes expectedAudioAttributes) throws Exception {
+        verify(mockPlayer).setDataSource(context, expectedUri);
+        verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
+        verify(mockPlayer).setPreferredDevice(null);
+        verify(mockPlayer).prepare();
+    }
+
+    /** Verify this local media player mock instance was setup with given fallback attributes. */
+    public static void verifyPlayerFallbackSetup(MediaPlayer mockPlayer,
+            AssetFileDescriptor afd, AudioAttributes expectedAudioAttributes) throws Exception {
+        // This is very specific but it's a simple way to test that the test resource matches.
+        if (afd.getDeclaredLength() < 0) {
+            verify(mockPlayer).setDataSource(afd.getFileDescriptor());
+        } else {
+            verify(mockPlayer).setDataSource(afd.getFileDescriptor(),
+                    afd.getStartOffset(),
+                    afd.getDeclaredLength());
+        }
+        verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
+        verify(mockPlayer).setPreferredDevice(null);
+        verify(mockPlayer).prepare();
+    }
+
+    private MediaPlayerTestHelper() {
+    }
+}
diff --git a/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java b/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java
new file mode 100644
index 0000000..25752ce
--- /dev/null
+++ b/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media.testing;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.when;
+
+import android.media.MediaPlayer;
+import android.media.Ringtone;
+import android.media.audiofx.HapticGenerator;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.Mockito;
+
+import java.util.ArrayDeque;
+import java.util.Map;
+import java.util.Queue;
+
+/**
+ * This rule ensures that all expected media player creations from the factory do actually
+ * occur. The reason for this level of control is that creating a media player is fairly
+ * expensive and blocking, so we do want unit tests of this class to "declare" interactions
+ * of all created media players.
+ * <p>
+ * This needs to be a TestRule so that the teardown assertions can be skipped if the test has
+ * failed (and media player assertions may just be a distracting side effect). Otherwise, the
+ * teardown failures hide the real test ones.
+ */
+public class RingtoneInjectablesTrackingTestRule implements TestRule {
+
+    private final Ringtone.Injectables mRingtoneTestInjectables = new TestInjectables();
+
+    // Queue of (local) media players, in order of expected creation. Enqueue using
+    // expectNewMediaPlayer(), dequeued by the media player factory passed to Ringtone.
+    // This queue is asserted to be empty at the end of the test.
+    private final Queue<MediaPlayer> mMockMediaPlayerQueue = new ArrayDeque<>();
+
+    // Similar to media players, but for haptic generator, which also needs releasing.
+    private final Map<MediaPlayer, HapticGenerator> mMockHapticGeneratorMap = new ArrayMap<>();
+
+    // Media players with haptic channels.
+    private final ArraySet<MediaPlayer> mHapticChannels = new ArraySet<>();
+
+    private boolean mHapticGeneratorAvailable = true;
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                base.evaluate();
+                // Only assert if the test didn't fail (base.evaluate() would throw).
+                assertWithMessage("Test setup an expectLocalMediaPlayer but it wasn't consumed")
+                        .that(mMockMediaPlayerQueue).isEmpty();
+                // Only assert if the test didn't fail (base.evaluate() would throw).
+                assertWithMessage(
+                        "Test setup an expectLocalHapticGenerator but it wasn't consumed")
+                        .that(mMockHapticGeneratorMap).isEmpty();
+            }
+        };
+    }
+
+    /** The {@link Ringtone.Injectables} to be used for creating a testable {@link Ringtone}. */
+    public Ringtone.Injectables getRingtoneTestInjectables() {
+        return mRingtoneTestInjectables;
+    }
+
+    /**
+     * Create a test {@link MediaPlayer} that will be provided to the {@link Ringtone} instance
+     * created with {@link #getRingtoneTestInjectables()}.
+     *
+     * <p>If a media player is not created during the test execution after this method is called
+     * then the test will fail. It will also fail if the ringtone attempts to create one without
+     * this method being called first.
+     */
+    public TestMediaPlayer expectLocalMediaPlayer() {
+        TestMediaPlayer mockMediaPlayer = Mockito.mock(TestMediaPlayer.class);
+        // Delegate to simulated methods. This means they can be verified but also reflect
+        // realistic transitions from the TestMediaPlayer.
+        doCallRealMethod().when(mockMediaPlayer).start();
+        doCallRealMethod().when(mockMediaPlayer).stop();
+        doCallRealMethod().when(mockMediaPlayer).setLooping(anyBoolean());
+        when(mockMediaPlayer.isLooping()).thenCallRealMethod();
+        mMockMediaPlayerQueue.add(mockMediaPlayer);
+        return mockMediaPlayer;
+    }
+
+    /**
+     * Create a test {@link HapticGenerator} that will be provided to the {@link Ringtone} instance
+     * created with {@link #getRingtoneTestInjectables()}.
+     *
+     * <p>If a haptic generator is not created during the test execution after this method is called
+     * then the test will fail. It will also fail if the ringtone attempts to create one without
+     * this method being called first.
+     */
+    public HapticGenerator expectHapticGenerator(MediaPlayer mediaPlayer) {
+        HapticGenerator mockHapticGenerator = Mockito.mock(HapticGenerator.class);
+        // A test should never want this.
+        assertWithMessage("Can't expect a second haptic generator created "
+                + "for one media player")
+                .that(mMockHapticGeneratorMap.put(mediaPlayer, mockHapticGenerator))
+                .isNull();
+        return mockHapticGenerator;
+    }
+
+    /**
+     * Configures the {@link MediaPlayer} to always return given flag when
+     * {@link Ringtone.Injectables#hasHapticChannels(MediaPlayer)} is called.
+     */
+    public void setHasHapticChannels(MediaPlayer mp, boolean hasHapticChannels) {
+        if (hasHapticChannels) {
+            mHapticChannels.add(mp);
+        } else {
+            mHapticChannels.remove(mp);
+        }
+    }
+
+    /** Test implementation of {@link Ringtone.Injectables} that uses the test rule setup. */
+    private class TestInjectables extends Ringtone.Injectables {
+        @Override
+        public MediaPlayer newMediaPlayer() {
+            assertWithMessage(
+                    "Unexpected MediaPlayer creation. Bug or need expectNewMediaPlayer")
+                    .that(mMockMediaPlayerQueue)
+                    .isNotEmpty();
+            return mMockMediaPlayerQueue.remove();
+        }
+
+        @Override
+        public boolean isHapticGeneratorAvailable() {
+            return mHapticGeneratorAvailable;
+        }
+
+        @Override
+        public HapticGenerator createHapticGenerator(MediaPlayer mediaPlayer) {
+            HapticGenerator mockHapticGenerator = mMockHapticGeneratorMap.remove(mediaPlayer);
+            assertWithMessage("Unexpected HapticGenerator creation. "
+                    + "Bug or need expectHapticGenerator")
+                    .that(mockHapticGenerator)
+                    .isNotNull();
+            return mockHapticGenerator;
+        }
+
+        @Override
+        public boolean isHapticPlaybackSupported() {
+            return true;
+        }
+
+        @Override
+        public boolean hasHapticChannels(MediaPlayer mp) {
+            return mHapticChannels.contains(mp);
+        }
+    }
+
+    /**
+     * MediaPlayer relies on a native backend and so its necessary to intercept calls from
+     * fake usage hitting them.
+     * <p>
+     * Mocks don't work directly on native calls, but if they're overridden then it does work.
+     * Some basic state faking is also done to make the mocks more realistic.
+     */
+    public static class TestMediaPlayer extends MediaPlayer {
+        private boolean mIsPlaying = false;
+        private boolean mIsLooping = false;
+
+        @Override
+        public void start() {
+            mIsPlaying = true;
+        }
+
+        @Override
+        public void stop() {
+            mIsPlaying = false;
+        }
+
+        @Override
+        public void setLooping(boolean value) {
+            mIsLooping = value;
+        }
+
+        @Override
+        public boolean isLooping() {
+            return mIsLooping;
+        }
+
+        @Override
+        public boolean isPlaying() {
+            return mIsPlaying;
+        }
+
+        /**
+         * Updates {@link #isPlaying()} result to false, if it's set to true.
+         *
+         * @throws IllegalStateException is {@link #isPlaying()} is already false
+         */
+        public void simulatePlayingFinished() {
+            if (!mIsPlaying) {
+                throw new IllegalStateException(
+                        "Attempted to pretend playing finished when not playing");
+            }
+            mIsPlaying = false;
+        }
+    }
+}
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-pl/strings.xml b/packages/CompanionDeviceManager/res/values-pl/strings.xml
index 7ca172e..4eb8de6 100644
--- a/packages/CompanionDeviceManager/res/values-pl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pl/strings.xml
@@ -37,7 +37,7 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Zezwolić urządzeniu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; na wykonanie tego działania?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> prosi w imieniu urządzenia <xliff:g id="DEVICE_NAME">%2$s</xliff:g> o uprawnienia do strumieniowego odtwarzania treści i innych funkcji systemowych na urządzeniach w pobliżu"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"urządzenie"</string>
-    <string name="summary_generic" msgid="1761976003668044801">"Ta aplikacja może synchronizować informacje takie jak nazwa osoby dzwoniącej między Twoim telefonem i wybranym urządzeniem"</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Ta aplikacja może synchronizować informacje takie jak imię i nazwisko osoby dzwoniącej między Twoim telefonem i wybranym urządzeniem"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Zezwól"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nie zezwalaj"</string>
     <string name="consent_cancel" msgid="5655005528379285841">"Anuluj"</string>
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/CredentialManager/res/values-hu/strings.xml b/packages/CredentialManager/res/values-hu/strings.xml
index 118a77c..c25fa99 100644
--- a/packages/CredentialManager/res/values-hu/strings.xml
+++ b/packages/CredentialManager/res/values-hu/strings.xml
@@ -70,7 +70,7 @@
     <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Elvetés"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Szeretné a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz mentett azonosítókulcsot használni?"</string>
     <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Szeretné az elmentett jelszavát használni a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Szeretné használni a következőhöz tartozó bejelentkezési adatait: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Szeretné használni bejelentkezési adatait a következőhöz: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Feloldja a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> bejelentkezési lehetőségeit?"</string>
     <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Mentett azonosítókulcs kiválasztása a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz"</string>
     <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Mentett jelszó kiválasztása a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz"</string>
diff --git a/packages/CredentialManager/res/values-ka/strings.xml b/packages/CredentialManager/res/values-ka/strings.xml
index ed42f3a..8125ec6 100644
--- a/packages/CredentialManager/res/values-ka/strings.xml
+++ b/packages/CredentialManager/res/values-ka/strings.xml
@@ -70,12 +70,12 @@
     <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"დახურვა"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"გსურთ თქვენი დამახსოვრებული წვდომის გასაღების გამოყენება აპისთვის: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"გამოიყენებთ შენახულ პაროლს <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"გსურთ შესვლის <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის გამოყენება?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"გსურთ შესვლის <xliff:g id="APP_NAME">%1$s</xliff:g>-ისთვის გამოყენება?"</string>
     <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"გსურთ შესვლის ვარიანტების განბლოკვა <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის?"</string>
     <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"აირჩიეთ შენახული წვდომის გასაღები <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის"</string>
     <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"აირჩიეთ შენახული პაროლი <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის"</string>
     <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"აირჩიეთ სისტემაში შესვლის ინფორმაცია <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"აირჩიეთ შესვლა <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"აირჩიეთ შესვლა <xliff:g id="APP_NAME">%1$s</xliff:g>-ისთვის"</string>
     <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"გსურთ აირჩიოთ ვარიანტი <xliff:g id="APP_NAME">%1$s</xliff:g>-ისთვის?"</string>
     <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"გსურთ ამ ინფორმაციის გამოყენება <xliff:g id="APP_NAME">%1$s</xliff:g>-ში?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"სხვა ხერხით შესვლა"</string>
diff --git a/packages/CredentialManager/res/values-ky/strings.xml b/packages/CredentialManager/res/values-ky/strings.xml
index b4c5670..e2de2ef 100644
--- a/packages/CredentialManager/res/values-ky/strings.xml
+++ b/packages/CredentialManager/res/values-ky/strings.xml
@@ -70,12 +70,12 @@
     <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Жабуу"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосуна кирүү үчүн сакталган ачкычты колдоносузбу?"</string>
     <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн сакталган сырсөздү колдоносузбу?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосуна кирүү жолун тандайсызбы?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосуна төмөнкү аккаунт менен киресизби?"</string>
     <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн кирүү параметрлеринин кулпусу ачылсынбы?"</string>
     <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн сакталган киргизүүчү ачкычты тандаңыз"</string>
     <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн сакталган сырсөздү тандаңыз"</string>
     <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн кирүү маалыматын тандаңыз"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосуна кирүү жолун тандаңыз"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосуна кайсы аккаунт менен киресиз:"</string>
     <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн параметр тандайсызбы?"</string>
     <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Бул маалыматты <xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосунда пайдаланасызбы?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Башка жол менен кирүү"</string>
diff --git a/packages/CredentialManager/res/values-zh-rCN/strings.xml b/packages/CredentialManager/res/values-zh-rCN/strings.xml
index 15a668a..222b865 100644
--- a/packages/CredentialManager/res/values-zh-rCN/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rCN/strings.xml
@@ -70,7 +70,7 @@
     <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"忽略"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"要使用您为“<xliff:g id="APP_NAME">%1$s</xliff:g>”保存的通行密钥吗?"</string>
     <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"要使用已保存的密码登录“<xliff:g id="APP_NAME">%1$s</xliff:g>”吗?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"使用您的<xliff:g id="APP_NAME">%1$s</xliff:g>登录凭据?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"是否使用您的<xliff:g id="APP_NAME">%1$s</xliff:g>登录凭据继续?"</string>
     <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"要解锁“<xliff:g id="APP_NAME">%1$s</xliff:g>”的登录选项吗?"</string>
     <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"选择一个已保存的通行密钥来登录“<xliff:g id="APP_NAME">%1$s</xliff:g>”"</string>
     <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"选择一个已保存的密码来登录“<xliff:g id="APP_NAME">%1$s</xliff:g>”"</string>
diff --git a/packages/CredentialManager/res/values-zh-rTW/strings.xml b/packages/CredentialManager/res/values-zh-rTW/strings.xml
index f14a5ce..c09bf86 100644
--- a/packages/CredentialManager/res/values-zh-rTW/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rTW/strings.xml
@@ -17,7 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="4539824758261855508">"憑證管理工具"</string>
+    <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"取消"</string>
     <string name="string_continue" msgid="1346732695941131882">"繼續"</string>
     <string name="string_more_options" msgid="2763852250269945472">"儲存其他方式"</string>
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 473d7b6f..477e61d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -61,14 +61,20 @@
 import androidx.credentials.provider.PublicKeyCredentialEntry
 import androidx.credentials.provider.RemoteEntry
 import org.json.JSONObject
+import android.credentials.flags.Flags
 import java.time.Instant
 
+
 fun getAppLabel(
     pm: PackageManager,
     appPackageName: String
 ): String? {
     return try {
-        val pkgInfo = getPackageInfo(pm, appPackageName)
+        val pkgInfo = if (Flags.instantAppsEnabled()) {
+            getPackageInfo(pm, appPackageName)
+        } else {
+            pm.getPackageInfo(appPackageName, PackageManager.PackageInfoFlags.of(0))
+        }
         val applicationInfo = checkNotNull(pkgInfo.applicationInfo)
         applicationInfo.loadSafeLabel(
             pm, 0f,
@@ -91,7 +97,14 @@
         // Test data has only package name not component name.
         // For test data usage only.
         try {
-            val pkgInfo = getPackageInfo(pm, providerFlattenedComponentName)
+            val pkgInfo = if (Flags.instantAppsEnabled()) {
+                getPackageInfo(pm, providerFlattenedComponentName)
+            } else {
+                pm.getPackageInfo(
+                        providerFlattenedComponentName,
+                        PackageManager.PackageInfoFlags.of(0)
+                )
+            }
             val applicationInfo = checkNotNull(pkgInfo.applicationInfo)
             providerLabel =
                 applicationInfo.loadSafeLabel(
@@ -115,7 +128,14 @@
             // Added for mdoc use case where the provider may not need to register a service and
             // instead only relies on the registration api.
             try {
-                val pkgInfo = getPackageInfo(pm, providerFlattenedComponentName)
+                val pkgInfo = if (Flags.instantAppsEnabled()) {
+                    getPackageInfo(pm, providerFlattenedComponentName)
+                } else {
+                    pm.getPackageInfo(
+                            component.packageName,
+                            PackageManager.PackageInfoFlags.of(0)
+                    )
+                }
                 val applicationInfo = checkNotNull(pkgInfo.applicationInfo)
                 providerLabel =
                     applicationInfo.loadSafeLabel(
@@ -143,12 +163,12 @@
     pm: PackageManager,
     packageName: String
 ): PackageInfo {
-    val flags = PackageManager.MATCH_INSTANT
+    val packageManagerFlags = PackageManager.MATCH_INSTANT
 
     return pm.getPackageInfo(
        packageName,
        PackageManager.PackageInfoFlags.of(
-               (flags).toLong())
+               (packageManagerFlags).toLong())
     )
 }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 2318bb9..9355517 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -27,14 +27,22 @@
 import android.os.CancellationSignal
 import android.os.OutcomeReceiver
 import android.service.autofill.AutofillService
+import android.service.autofill.Dataset
+import android.service.autofill.Field
 import android.service.autofill.FillCallback
 import android.service.autofill.FillRequest
 import android.service.autofill.FillResponse
+import android.service.autofill.InlinePresentation
+import android.service.autofill.Presentations
 import android.service.autofill.SaveCallback
 import android.service.autofill.SaveRequest
 import android.service.credentials.CredentialProviderService
 import android.util.Log
 import android.view.autofill.AutofillId
+import org.json.JSONException
+import android.widget.inline.InlinePresentationSpec
+import androidx.autofill.inline.v1.InlineSuggestionUi
+import com.android.credentialmanager.GetFlowUtils
 import org.json.JSONObject
 import java.util.concurrent.Executors
 
@@ -49,11 +57,9 @@
         private const val SYS_PROVIDER_REQ_KEY = "isSystemProviderRequired"
         private const val CRED_OPTIONS_KEY = "credentialOptions"
         private const val TYPE_KEY = "type"
+        private const val REQ_TYPE_KEY = "get"
     }
 
-    private val credentialManager: CredentialManager =
-            getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager
-
     override fun onFillRequest(
             request: FillRequest,
             cancellationSignal: CancellationSignal,
@@ -66,16 +72,24 @@
 
         val getCredRequest: GetCredentialRequest? = getCredManRequest(structure)
         if (getCredRequest == null) {
+            Log.i(TAG, "No credential manager request found")
             callback.onFailure("No credential manager request found")
             return
         }
+        val credentialManager: CredentialManager =
+                getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager
 
         val outcome = object : OutcomeReceiver<GetCandidateCredentialsResponse,
                 GetCandidateCredentialsException> {
             override fun onResult(result: GetCandidateCredentialsResponse) {
                 Log.i(TAG, "getCandidateCredentials onResponse")
-                val fillResponse: FillResponse? = convertToFillResponse(result, request)
-                callback.onSuccess(fillResponse)
+                val fillResponse = convertToFillResponse(result, request)
+                if (fillResponse != null) {
+                    callback.onSuccess(fillResponse)
+                } else {
+                    Log.e(TAG, "Failed to create a FillResponse from the CredentialResponse.")
+                    callback.onFailure("No dataset was created from the CredentialResponse")
+                }
             }
 
             override fun onError(error: GetCandidateCredentialsException) {
@@ -97,7 +111,74 @@
             getCredResponse: GetCandidateCredentialsResponse,
             filLRequest: FillRequest
     ): FillResponse? {
-        TODO("Not yet implemented")
+        val providerList = GetFlowUtils.toProviderList(
+                getCredResponse.candidateProviderDataList,
+                this@CredentialAutofillService)
+        var totalEntryCount = 0
+        providerList.forEach { provider ->
+            totalEntryCount += provider.credentialEntryList.size
+        }
+        val inlineSuggestionsRequest = filLRequest.inlineSuggestionsRequest
+        val inlineMaxSuggestedCount = inlineSuggestionsRequest?.maxSuggestionCount ?: 0
+        val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
+        val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
+        var maxItemCount = totalEntryCount
+        if (inlineMaxSuggestedCount > 0) {
+            maxItemCount = maxItemCount.coerceAtMost(inlineMaxSuggestedCount)
+        }
+        var i = 0
+        val fillResponseBuilder = FillResponse.Builder()
+        var emptyFillResponse = true
+        providerList.forEach {provider ->
+            // TODO(b/299321128): Before iterating the list, sort the list so that
+            //  the relevant entries don't get truncated
+            provider.credentialEntryList.forEach entryLoop@ {entry ->
+                val autofillId: AutofillId? = entry.fillInIntent?.getParcelableExtra(
+                        CredentialProviderService.EXTRA_AUTOFILL_ID,
+                        AutofillId::class.java)
+                val pendingIntent = entry.pendingIntent
+                if (autofillId == null || pendingIntent == null) {
+                    return@entryLoop
+                }
+                var inlinePresentation: InlinePresentation? = null
+                // Create inline presentation
+                if (inlinePresentationSpecs != null && i < maxItemCount) {
+                    val spec: InlinePresentationSpec
+                    if (i < inlinePresentationSpecsCount) {
+                        spec = inlinePresentationSpecs[i]
+                    } else {
+                        spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
+                    }
+                    val sliceBuilder = InlineSuggestionUi
+                            .newContentBuilder(pendingIntent)
+                            .setTitle(entry.userName)
+                    inlinePresentation = InlinePresentation(
+                            sliceBuilder.build().slice, spec, /* pinned= */ false)
+                }
+                i++
+
+                val dataSetBuilder = Dataset.Builder()
+                val presentationBuilder = Presentations.Builder()
+                if (inlinePresentation != null) {
+                    presentationBuilder.setInlinePresentation(inlinePresentation)
+                }
+                fillResponseBuilder.addDataset(
+                        dataSetBuilder
+                                .setField(
+                                        autofillId,
+                                        Field.Builder().setPresentations(
+                                                presentationBuilder.build())
+                                                .build())
+                                .setAuthentication(entry.pendingIntent.intentSender)
+                                .setAuthenticationExtras(entry.fillInIntent.extras)
+                                .build())
+                emptyFillResponse = false
+            }
+        }
+        if (emptyFillResponse) {
+            return null
+        }
+        return fillResponseBuilder.build()
     }
 
     override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
@@ -165,8 +246,12 @@
 
         val credentialOptions: MutableList<CredentialOption> = mutableListOf()
         for (credentialHint in credentialHints) {
-            convertJsonToCredentialOption(credentialHint, autofillId)
-                    .let { credentialOptions.addAll(it) }
+            try {
+                convertJsonToCredentialOption(credentialHint, autofillId)
+                        .let { credentialOptions.addAll(it) }
+            } catch (e: JSONException) {
+                Log.i(TAG, "Exception while parsing response: " + e.message)
+            }
         }
         return credentialOptions
     }
@@ -178,7 +263,8 @@
         val credentialOptions: MutableList<CredentialOption> = mutableListOf()
 
         val json = JSONObject(jsonString)
-        val options = json.getJSONArray(CRED_OPTIONS_KEY)
+        val jsonGet = json.getJSONObject(REQ_TYPE_KEY)
+        val options = jsonGet.getJSONArray(CRED_OPTIONS_KEY)
         for (i in 0 until options.length()) {
             val option = options.getJSONObject(i)
             val candidateBundle = convertJsonToBundle(option.getJSONObject(CANDIDATE_DATA_KEY))
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/InputDevices/res/raw/keyboard_layout_brazilian.kcm b/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm
index ad3199f..d9338b1 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm
@@ -337,7 +337,7 @@
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
-    shift+capslock:                     'n'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_persian.kcm b/packages/InputDevices/res/raw/keyboard_layout_persian.kcm
index 6744922..fc53cba 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_persian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_persian.kcm
@@ -12,9 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
-
-type FULL
+type OVERLAY
 
 ### Basic QWERTY keys ###
 
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 88bb30b..7f23f74 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -227,13 +227,6 @@
         android:label="@string/keyboard_layout_turkish"
         android:keyboardLayout="@raw/keyboard_layout_turkish"
         android:keyboardLocale="tr-Latn"
-        android:keyboardLayoutType="qwerty" />
-
-    <keyboard-layout
-        android:name="keyboard_layout_turkish"
-        android:label="@string/keyboard_layout_turkish"
-        android:keyboardLayout="@raw/keyboard_layout_turkish"
-        android:keyboardLocale="tr-Latn"
         android:keyboardLayoutType="turkish_q" />
 
     <keyboard-layout
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index d97fb54..c5ae4a3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -381,7 +381,7 @@
             final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
                     -1 /* defaultValue */);
             final SessionInfo info = mInstaller.getSessionInfo(sessionId);
-            String resolvedPath = info.getResolvedBaseApkPath();
+            String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null;
             if (info == null || !info.isSealed() || resolvedPath == null) {
                 Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
                 finish();
@@ -609,7 +609,7 @@
                 CharSequence label = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
                 if (mLocalLOGV) Log.i(TAG, "creating snippet for " + label);
                 mAppSnippet = new PackageUtil.AppSnippet(label,
-                        mPm.getApplicationIcon(mPkgInfo.applicationInfo));
+                        mPm.getApplicationIcon(mPkgInfo.applicationInfo), getBaseContext());
             } break;
 
             case ContentResolver.SCHEME_FILE: {
@@ -647,7 +647,7 @@
         mPkgInfo = generateStubPackageInfo(info.getAppPackageName());
         mAppSnippet = new PackageUtil.AppSnippet(info.getAppLabel(),
                 info.getAppIcon() != null ? new BitmapDrawable(getResources(), info.getAppIcon())
-                        : getPackageManager().getDefaultActivityIcon());
+                        : getPackageManager().getDefaultActivityIcon(), getBaseContext());
         return true;
     }
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
index 334886f..976a3ad 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
@@ -18,6 +18,7 @@
 package com.android.packageinstaller;
 
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
@@ -135,15 +136,20 @@
     static final class AppSnippet implements Parcelable {
         @NonNull public CharSequence label;
         @Nullable public Drawable icon;
-        public AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon) {
+        public int iconSize;
+
+        AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon, Context context) {
             this.label = label;
             this.icon = icon;
+            final ActivityManager am = context.getSystemService(ActivityManager.class);
+            this.iconSize = am.getLauncherLargeIconSize();
         }
 
         private AppSnippet(Parcel in) {
             label = in.readString();
             Bitmap bmp = in.readParcelable(getClass().getClassLoader(), Bitmap.class);
             icon = new BitmapDrawable(Resources.getSystem(), bmp);
+            iconSize = in.readInt();
         }
 
         @Override
@@ -161,6 +167,7 @@
             dest.writeString(label.toString());
             Bitmap bmp = getBitmapFromDrawable(icon);
             dest.writeParcelable(bmp, 0);
+            dest.writeInt(iconSize);
         }
 
         private Bitmap getBitmapFromDrawable(Drawable drawable) {
@@ -174,6 +181,14 @@
             // bitmap held within
             drawable.draw(canvas);
 
+            // Scale it down if the icon is too large
+            if ((bmp.getWidth() > iconSize * 2) || (bmp.getHeight() > iconSize * 2)) {
+                Bitmap scaledBitmap = Bitmap.createScaledBitmap(bmp, iconSize, iconSize, true);
+                if (scaledBitmap != bmp) {
+                    bmp.recycle();
+                }
+                return scaledBitmap;
+            }
             return bmp;
         }
 
@@ -241,7 +256,7 @@
         } catch (OutOfMemoryError e) {
             Log.i(LOG_TAG, "Could not load app icon", e);
         }
-        return new PackageUtil.AppSnippet(label, icon);
+        return new PackageUtil.AppSnippet(label, icon, pContext);
     }
 
     private static String findFilePath(File[] files, String postfix) {
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 2d231f2..8964ada 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -11,29 +11,42 @@
     name: "SettingsLib",
 
     static_libs: [
-        "androidx.annotation_annotation",
-        "androidx.appcompat_appcompat",
-        "androidx.coordinatorlayout_coordinatorlayout",
-        "androidx.core_core",
-        "androidx.fragment_fragment",
-        "androidx.lifecycle_lifecycle-runtime",
-        "androidx.loader_loader",
         "androidx.localbroadcastmanager_localbroadcastmanager",
-        "androidx.preference_preference",
-        "androidx.recyclerview_recyclerview",
-        "com.google.android.material_material",
-        "iconloader",
+        "androidx.room_room-runtime",
+        "zxing-core",
 
         "WifiTrackerLibRes",
+        "iconloader",
+        "setupdesign",
+
+        "SettingsLibActionBarShadow",
+        "SettingsLibActionButtonsPreference",
+        "SettingsLibAdaptiveIcon",
+        "SettingsLibAppPreference",
+        "SettingsLibBannerMessagePreference",
+        "SettingsLibBarChartPreference",
+        "SettingsLibButtonPreference",
+        "SettingsLibCollapsingToolbarBaseActivity",
         "SettingsLibDeviceStateRotationLock",
         "SettingsLibDisplayUtils",
         "SettingsLibEmergencyNumber",
+        "SettingsLibEntityHeaderWidgets",
+        "SettingsLibFooterPreference",
+        "SettingsLibHelpUtils",
+        "SettingsLibIllustrationPreference",
+        "SettingsLibLayoutPreference",
+        "SettingsLibMainSwitchPreference",
+        "SettingsLibProfileSelector",
+        "SettingsLibProgressBar",
+        "SettingsLibRestrictedLockUtils",
         "SettingsLibSearchWidget",
+        "SettingsLibSelectorWithWidgetPreference",
+        "SettingsLibSettingsSpinner",
+        "SettingsLibSettingsTransition",
+        "SettingsLibTopIntroPreference",
+        "SettingsLibTwoTargetPreference",
+        "SettingsLibUsageProgressBarPreference",
         "SettingsLibUtils",
-        "SettingsLibWidget",
-        "setupdesign",
-        "zxing-core-1.7",
-        "androidx.room_room-runtime",
         "settingslib_flags_lib",
     ],
 
@@ -47,56 +60,10 @@
     ],
 }
 
-// Group all the libraries with namespace "com.android.settingslib.widget", to allow SettingsLib to
-// set use_resource_processor = true.
-// We can remove SettingsLibWidget when all these libraries have its own namespace.
-android_library {
-    name: "SettingsLibWidget",
-    visibility: ["//visibility:private"],
-    manifest: "AndroidManifest-SettingsLibWidget.xml",
-    static_libs: [
-        "SettingsLibActionBarShadow",
-        "SettingsLibActionButtonsPreference",
-        "SettingsLibAdaptiveIcon",
-        "SettingsLibAppPreference",
-        "SettingsLibBannerMessagePreference",
-        "SettingsLibBarChartPreference",
-        "SettingsLibButtonPreference",
-        "SettingsLibCollapsingToolbarBaseActivity",
-        "SettingsLibEntityHeaderWidgets",
-        "SettingsLibFooterPreference",
-        "SettingsLibHelpUtils",
-        "SettingsLibIllustrationPreference",
-        "SettingsLibLayoutPreference",
-        "SettingsLibMainSwitchPreference",
-        "SettingsLibProfileSelector",
-        "SettingsLibProgressBar",
-        "SettingsLibRestrictedLockUtils",
-        "SettingsLibSelectorWithWidgetPreference",
-        "SettingsLibSettingsSpinner",
-        "SettingsLibSettingsTransition",
-        "SettingsLibTopIntroPreference",
-        "SettingsLibTwoTargetPreference",
-        "SettingsLibUsageProgressBarPreference",
-    ],
-
-    resource_dirs: [],
-}
-
 // NOTE: Keep this module in sync with ./common.mk
 java_defaults {
     name: "SettingsLibDefaults",
     static_libs: [
-        "androidx.annotation_annotation",
-        "androidx.appcompat_appcompat",
-        "androidx.coordinatorlayout_coordinatorlayout",
-        "androidx.core_core",
-        "androidx.fragment_fragment",
-        "androidx.lifecycle_lifecycle-runtime",
-        "androidx.loader_loader",
-        "androidx.localbroadcastmanager_localbroadcastmanager",
-        "androidx.preference_preference",
-        "androidx.recyclerview_recyclerview",
         "SettingsLib",
     ],
 }
diff --git a/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml b/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml
deleted file mode 100644
index 38a7d6a..0000000
--- a/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?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 package="com.android.settingslib.widget" />
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index 33aa985..4871ef3 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -9,6 +9,7 @@
 
 android_library {
     name: "SettingsLibMainSwitchPreference",
+    use_resource_processor: true,
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 56b3eac..6001155 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -31,12 +31,11 @@
 import androidx.annotation.ColorInt;
 
 import com.android.settingslib.utils.BuildCompatUtils;
+import com.android.settingslib.widget.mainswitch.R;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import com.android.settingslib.widget.mainswitch.R;
-
 /**
  * MainSwitchBar is a View with a customized Switch.
  * This component is used as the main switch of the page
@@ -77,7 +76,7 @@
             final TypedArray a = context.obtainStyledAttributes(
                     new int[]{android.R.attr.colorAccent});
             mBackgroundActivatedColor = a.getColor(0, 0);
-            mBackgroundColor = context.getColor(R.color.material_grey_600);
+            mBackgroundColor = context.getColor(androidx.appcompat.R.color.material_grey_600);
             a.recycle();
         }
 
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 1cc2867..0b7a568 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -26,7 +26,7 @@
 }
 
 allprojects {
-    extra["jetpackComposeVersion"] = "1.6.0-alpha02"
+    extra["jetpackComposeVersion"] = "1.6.0-alpha07"
 }
 
 subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
similarity index 96%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
index df1d7d1..b001cad 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
@@ -18,8 +18,8 @@
 
 import android.os.Bundle
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.Launch
 import androidx.compose.material.icons.outlined.Delete
-import androidx.compose.material.icons.outlined.Launch
 import androidx.compose.material.icons.outlined.WarningAmber
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.tooling.preview.Preview
@@ -47,7 +47,7 @@
     override fun Page(arguments: Bundle?) {
         RegularScaffold(title = TITLE) {
             val actionButtons = listOf(
-                ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+                ActionButton(text = "Open", imageVector = Icons.AutoMirrored.Outlined.Launch) {},
                 ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {},
                 ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {},
             )
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 8b56336..aafae5f 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,11 +15,11 @@
 #
 
 [versions]
-agp = "8.1.1"
+agp = "8.1.2"
 compose-compiler = "1.5.1"
 dexmaker-mockito = "2.28.3"
 kotlin = "1.9.0"
-truth = "1.1"
+truth = "1.1.5"
 
 [libraries]
 dexmaker-mockito = { module = "com.linkedin.dexmaker:dexmaker-mockito", version.ref = "dexmaker-mockito" }
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index da04f42..ce89de6 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -16,7 +16,7 @@
 
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
 networkTimeout=10000
 validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index b810511..b73bbd8 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,13 +57,13 @@
     api("androidx.slice:slice-builders:1.1.0-alpha02")
     api("androidx.slice:slice-core:1.1.0-alpha02")
     api("androidx.slice:slice-view:1.1.0-alpha02")
-    api("androidx.compose.material3:material3:1.2.0-alpha04")
+    api("androidx.compose.material3:material3:1.2.0-alpha09")
     api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
     api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
     api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
     api("androidx.lifecycle:lifecycle-livedata-ktx")
     api("androidx.lifecycle:lifecycle-runtime-compose")
-    api("androidx.navigation:navigation-compose:2.7.1")
+    api("androidx.navigation:navigation-compose:2.7.4")
     api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
     api("com.google.android.material:material:1.7.0-alpha03")
     debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
index 0552c40..1ad075c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
@@ -31,7 +31,6 @@
 import androidx.compose.foundation.layout.width
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.Delete
-import androidx.compose.material.icons.outlined.Launch
 import androidx.compose.material.icons.outlined.WarningAmber
 import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.FilledTonalButton
@@ -52,6 +51,7 @@
 import com.android.settingslib.spa.framework.theme.SettingsShape
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.framework.theme.divider
+import androidx.compose.material.icons.automirrored.outlined.Launch
 
 data class ActionButton(
     val text: String,
@@ -101,7 +101,9 @@
                 modifier = Modifier.size(SettingsDimension.itemIconSize),
             )
             Box(
-                modifier = Modifier.padding(top = 4.dp).fillMaxHeight(),
+                modifier = Modifier
+                    .padding(top = 4.dp)
+                    .fillMaxHeight(),
                 contentAlignment = Alignment.Center,
             ) {
                 Text(
@@ -129,7 +131,7 @@
     SettingsTheme {
         ActionButtons(
             listOf(
-                ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+                ActionButton(text = "Open", imageVector = Icons.AutoMirrored.Outlined.Launch) {},
                 ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {},
                 ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {},
             )
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
index 62189dc..6ef4590 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
@@ -18,7 +18,6 @@
 
 import androidx.appcompat.R
 import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.ArrowBack
 import androidx.compose.material.icons.outlined.Clear
 import androidx.compose.material.icons.outlined.FindInPage
 import androidx.compose.material3.Icon
@@ -31,6 +30,7 @@
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.LayoutDirection
 import com.android.settingslib.spa.framework.compose.LocalNavController
+import androidx.compose.material.icons.automirrored.outlined.ArrowBack
 
 /** Action that navigates back to last page. */
 @Composable
@@ -53,7 +53,7 @@
 private fun BackAction(contentDescription: String, onClick: () -> Unit) {
     IconButton(onClick) {
         Icon(
-            imageVector = Icons.Outlined.ArrowBack,
+            imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
             contentDescription = contentDescription,
             modifier = Modifier.autoMirrored(),
         )
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
index aa148b0..9f7f040 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
@@ -21,7 +21,7 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.pager.HorizontalPager
 import androidx.compose.foundation.pager.rememberPagerState
-import androidx.compose.material3.TabRow
+import androidx.compose.material3.PrimaryTabRow
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
@@ -43,7 +43,7 @@
         val coroutineScope = rememberCoroutineScope()
         val pagerState = rememberPagerState { titles.size }
 
-        TabRow(
+        PrimaryTabRow(
             selectedTabIndex = pagerState.currentPage,
             modifier = Modifier.padding(horizontal = SettingsDimension.itemPaddingEnd),
             containerColor = Color.Transparent,
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
index f59b0de..8d9bac6 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
@@ -17,8 +17,8 @@
 package com.android.settingslib.spa.widget.button
 
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.Launch
 import androidx.compose.material.icons.outlined.Close
-import androidx.compose.material.icons.outlined.Launch
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
@@ -43,7 +43,10 @@
         composeTestRule.setContent {
             ActionButtons(
                 listOf(
-                    ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+                    ActionButton(
+                        text = "Open",
+                        imageVector = Icons.AutoMirrored.Outlined.Launch
+                    ) {},
                 )
             )
         }
@@ -57,7 +60,7 @@
         composeTestRule.setContent {
             ActionButtons(
                 listOf(
-                    ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {
+                    ActionButton(text = "Open", imageVector = Icons.AutoMirrored.Outlined.Launch) {
                         clicked = true
                     },
                 )
@@ -74,7 +77,10 @@
         composeTestRule.setContent {
             ActionButtons(
                 listOf(
-                    ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+                    ActionButton(
+                        text = "Open",
+                        imageVector = Icons.AutoMirrored.Outlined.Launch
+                    ) {},
                     ActionButton(text = "Close", imageVector = Icons.Outlined.Close) {},
                 )
             )
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle.kts b/packages/SettingsLib/Spa/testutils/build.gradle.kts
index 50243dc..cce8235 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle.kts
+++ b/packages/SettingsLib/Spa/testutils/build.gradle.kts
@@ -41,7 +41,7 @@
     api("androidx.arch.core:core-testing:2.2.0-alpha01")
     api("androidx.compose.ui:ui-test-junit4:$jetpackComposeVersion")
     api("androidx.lifecycle:lifecycle-runtime-testing")
-    api("org.mockito.kotlin:mockito-kotlin:5.1.0")
+    api("org.mockito.kotlin:mockito-kotlin:2.2.11")
     api("org.mockito:mockito-core") {
         version {
             strictly("2.28.2")
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
index dfd8f6b..3525037 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
@@ -18,8 +18,10 @@
 
 import android.app.admin.DevicePolicyManager
 import android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER
+import android.app.admin.DevicePolicyResources.Strings.Settings.PRIVATE_CATEGORY_HEADER
 import android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER
 import android.content.Context
+import android.content.pm.UserInfo
 import com.android.settingslib.R
 
 class EnterpriseRepository(private val context: Context) {
@@ -30,8 +32,10 @@
     fun getEnterpriseString(updatableStringId: String, resId: Int): String =
         checkNotNull(resources.getString(updatableStringId) { context.getString(resId) })
 
-    fun getProfileTitle(isManagedProfile: Boolean): String = if (isManagedProfile) {
+    fun getProfileTitle(userInfo: UserInfo): String = if (userInfo.isManagedProfile) {
         getEnterpriseString(WORK_CATEGORY_HEADER, R.string.category_work)
+    } else if (userInfo.isPrivateProfile) {
+        getEnterpriseString(PRIVATE_CATEGORY_HEADER, R.string.category_private)
     } else {
         getEnterpriseString(PERSONAL_CATEGORY_HEADER, R.string.category_personal)
     }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index f54de15..09cb98e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -22,6 +22,8 @@
 import android.os.UserManager
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settingslib.RestrictedLockUtils
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
@@ -32,15 +34,15 @@
 import com.android.settingslib.widget.restricted.R
 
 data class Restrictions(
-    val userId: Int,
+    val userId: Int = UserHandle.myUserId(),
     val keys: List<String>,
 )
 
 sealed interface RestrictedMode
 
-object NoRestricted : RestrictedMode
+data object NoRestricted : RestrictedMode
 
-object BaseUserRestricted : RestrictedMode
+data object BaseUserRestricted : RestrictedMode
 
 interface BlockedByAdmin : RestrictedMode {
     fun getSummary(checked: Boolean?): String
@@ -79,6 +81,17 @@
 
 typealias RestrictionsProviderFactory = (Context, Restrictions) -> RestrictionsProvider
 
+@Composable
+internal fun RestrictionsProviderFactory.rememberRestrictedMode(
+    restrictions: Restrictions,
+): State<RestrictedMode?> {
+    val context = LocalContext.current
+    val restrictionsProvider = remember(restrictions) {
+        this(context, restrictions)
+    }
+    return restrictionsProvider.restrictedModeState()
+}
+
 internal class RestrictionsProviderImpl(
     private val context: Context,
     private val restrictions: Restrictions,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 1fa854a..17e9708 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -45,6 +45,7 @@
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
 import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
 import kotlinx.coroutines.flow.Flow
 
@@ -149,14 +150,13 @@
 
     @Composable
     fun getSummary(record: T): State<String> {
-        val restrictionsProvider = remember(record.app.userId) {
-            val restrictions = Restrictions(
+        val restrictions = remember(record.app.userId) {
+            Restrictions(
                 userId = record.app.userId,
                 keys = listModel.switchRestrictionKeys,
             )
-            restrictionsProviderFactory(context, restrictions)
         }
-        val restrictedMode = restrictionsProvider.restrictedModeState()
+        val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions)
         val allowed = listModel.isAllowed(record)
         return remember {
             derivedStateOf {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
index 6a76c93..5447f21 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
@@ -45,7 +45,7 @@
         val enterpriseRepository = EnterpriseRepository(context)
         userGroups.map { userGroup ->
             enterpriseRepository.getProfileTitle(
-                isManagedProfile = userGroup.userInfos.first().isManagedProfile,
+                userGroup.userInfos.first(),
             )
         }
     }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
new file mode 100644
index 0000000..50490c0
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
@@ -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.settingslib.spaprivileged.template.preference
+
+import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
+
+@Composable
+fun RestrictedPreference(
+    model: PreferenceModel,
+    restrictions: Restrictions,
+) {
+    RestrictedPreference(model, restrictions, ::RestrictionsProviderImpl)
+}
+
+@VisibleForTesting
+@Composable
+internal fun RestrictedPreference(
+    model: PreferenceModel,
+    restrictions: Restrictions,
+    restrictionsProviderFactory: RestrictionsProviderFactory,
+) {
+    if (restrictions.keys.isEmpty()) {
+        Preference(model)
+        return
+    }
+    val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
+    val restrictedSwitchModel = remember(restrictedMode) {
+        RestrictedPreferenceModel(model, restrictedMode)
+    }
+    restrictedSwitchModel.RestrictionWrapper {
+        Preference(restrictedSwitchModel)
+    }
+}
+
+private class RestrictedPreferenceModel(
+    model: PreferenceModel,
+    private val restrictedMode: RestrictedMode?,
+) : PreferenceModel {
+    override val title = model.title
+    override val summary = model.summary
+    override val icon = model.icon
+
+    override val enabled = when (restrictedMode) {
+        NoRestricted -> model.enabled
+        else -> stateOf(false)
+    }
+
+    override val onClick = when (restrictedMode) {
+        NoRestricted -> model.onClick
+        // Need to passthrough onClick for clickable semantics, although since enabled is false so
+        // this will not be called.
+        BaseUserRestricted -> model.onClick
+        else -> null
+    }
+
+    @Composable
+    fun RestrictionWrapper(content: @Composable () -> Unit) {
+        if (restrictedMode !is BlockedByAdmin) {
+            content()
+            return
+        }
+        Box(
+            Modifier
+                .clickable(
+                    role = Role.Button,
+                    onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() },
+                )
+        ) { content() }
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
index e77dcd4..2129403 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -17,6 +17,7 @@
 package com.android.settingslib.spaprivileged.template.preference
 
 import android.content.Context
+import androidx.annotation.VisibleForTesting
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
@@ -40,22 +41,29 @@
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
 
 @Composable
 fun RestrictedSwitchPreference(
     model: SwitchPreferenceModel,
     restrictions: Restrictions,
-    restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl,
+) {
+    RestrictedSwitchPreference(model, restrictions, ::RestrictionsProviderImpl)
+}
+
+@VisibleForTesting
+@Composable
+internal fun RestrictedSwitchPreference(
+    model: SwitchPreferenceModel,
+    restrictions: Restrictions,
+    restrictionsProviderFactory: RestrictionsProviderFactory,
 ) {
     if (restrictions.keys.isEmpty()) {
         SwitchPreference(model)
         return
     }
     val context = LocalContext.current
-    val restrictionsProvider = remember(restrictions) {
-        restrictionsProviderFactory(context, restrictions)
-    }
-    val restrictedMode = restrictionsProvider.restrictedModeState().value
+    val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
     val restrictedSwitchModel = remember(restrictedMode) {
         RestrictedSwitchPreferenceModel(context, model, restrictedMode)
     }
@@ -112,8 +120,8 @@
     override val onCheckedChange = when (restrictedMode) {
         null -> null
         is NoRestricted -> model.onCheckedChange
-        // Need to pass a non null onCheckedChange to enable semantics ToggleableState, although
-        // since changeable is false this will not be called.
+        // Need to passthrough onCheckedChange for toggleable semantics, although since changeable
+        // is false so this will not be called.
         is BaseUserRestricted -> model.onCheckedChange
         // Pass null since semantics ToggleableState is provided in RestrictionWrapper.
         is BlockedByAdmin -> null
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
index 86b6f02..f9abefc 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
@@ -16,15 +16,15 @@
 
 package com.android.settingslib.spaprivileged.template.scaffold
 
+import androidx.annotation.VisibleForTesting
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
 import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope
 import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
 import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
 
 @Composable
 fun MoreOptionsScope.RestrictedMenuItem(
@@ -35,6 +35,7 @@
     RestrictedMenuItemImpl(text, restrictions, onClick, ::RestrictionsProviderImpl)
 }
 
+@VisibleForTesting
 @Composable
 internal fun MoreOptionsScope.RestrictedMenuItemImpl(
     text: String,
@@ -42,12 +43,8 @@
     onClick: () -> Unit,
     restrictionsProviderFactory: RestrictionsProviderFactory,
 ) {
-    val context = LocalContext.current
-    val restrictionsProvider = remember(restrictions) {
-        restrictionsProviderFactory(context, restrictions)
-    }
-    val restrictedMode = restrictionsProvider.restrictedModeState().value
-    MenuItem(text = text, enabled = restrictedMode !is BaseUserRestricted) {
+    val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
+    MenuItem(text = text, enabled = restrictedMode !== BaseUserRestricted) {
         when (restrictedMode) {
             is BlockedByAdmin -> restrictedMode.sendShowAdminSupportDetailsIntent()
             else -> onClick()
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt
new file mode 100644
index 0000000..eadf0ca
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.settingslib.spaprivileged.template.preference
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RestrictedPreferenceTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+
+    private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
+    private var clicked = false
+
+    private val preferenceModel = object : PreferenceModel {
+        override val title = TITLE
+        override val onClick = { clicked = true }
+    }
+
+    @Test
+    fun whenRestrictionsKeysIsEmpty_enabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun whenRestrictionsKeysIsEmpty_clickable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(clicked).isTrue()
+    }
+
+    @Test
+    fun whenNoRestricted_enabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun whenNoRestricted_clickable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(clicked).isTrue()
+    }
+
+    @Test
+    fun whenBaseUserRestricted_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsNotEnabled()
+    }
+
+    @Test
+    fun whenBaseUserRestricted_notClickable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(clicked).isFalse()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_widgetInEnableStateToAllowClick() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_click() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+    }
+
+    private fun setContent(restrictions: Restrictions) {
+        composeTestRule.setContent {
+            RestrictedPreference(preferenceModel, restrictions) { _, _ ->
+                fakeRestrictionsProvider
+            }
+        }
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        const val USER_ID = 0
+        const val RESTRICTION_KEY = "restriction_key"
+    }
+}
diff --git a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
index d1bcb57..4936f88 100644
--- a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
@@ -5,4 +5,11 @@
   namespace: "media_solutions"
   description: "Gates whether to use a MediaRouter2-based implementation of InfoMediaManager, instead of the legacy MediaRouter2Manager-based implementation."
   bug: "192657812"
+}
+
+flag {
+  name: "enable_tv_media_output_dialog"
+  namespace: "tv_system_ui"
+  description: "Gates all the changes for the tv specific media output dialog"
+  bug: "303205631"
 }
\ No newline at end of file
diff --git a/packages/SettingsLib/common.mk b/packages/SettingsLib/common.mk
index d959656..431fd44 100644
--- a/packages/SettingsLib/common.mk
+++ b/packages/SettingsLib/common.mk
@@ -18,18 +18,5 @@
 # to the corresponding module.
 # NOTE: keep this file and ./Android.bp in sync.
 
-LOCAL_STATIC_JAVA_LIBRARIES += \
-    androidx.annotation_annotation
-
 LOCAL_STATIC_ANDROID_LIBRARIES += \
-    androidx.appcompat_appcompat \
-    androidx.coordinatorlayout_coordinatorlayout \
-    androidx.core_core \
-    androidx.fragment_fragment \
-    androidx.lifecycle_lifecycle-runtime \
-    androidx.loader_loader \
-    androidx.localbroadcastmanager_localbroadcastmanager \
-    androidx.preference_preference \
-    androidx.recyclerview_recyclerview \
     SettingsLib
-
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 53db192..afdb92b 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi twee stawe."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi drie stawe."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi-sein vol."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Gekoppel aan jou toestel."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Oop netwerk"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Veilige netwerk"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android-bedryfstelsel"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 77c9a97..ea8491b 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"ሁለት የWiFi አሞሌዎች።"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"ሦስት የWiFi አሞሌዎች።"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"የWiFi ምልክት ሙሉ ነው።"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"ከመሣሪያዎ ጋር ተገናኝቷል።"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"አውታረ መረብ ክፈት"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"ደህንነቱ የተጠበቀ አውታረ መረብ"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android  ስርዓተ ክወና"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 30d011a..c29e691 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"‏إشارة Wi-Fi تتكون من شريطين."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"‏إشارة Wi-Fi تتكون من ثلاثة أشرطة."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"‏إشارة Wi-Fi كاملة."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"تم الاتصال بجهازك."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"شبكة مفتوحة"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"شبكة محمية بكلمة مرور"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"‏نظام التشغيل Android"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 31aff6a..8eff4f6 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"ৱাই-ফাইৰ দুডাল দণ্ড।"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"ৱাই-ফাইৰ তিনিডাল দণ্ড।"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"ৱাই-ফাই সংকেত সৰ্বোচ্চ।"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"আপোনাৰ ডিভাইচটোৰ সৈতে সংযোগ কৰা আছে।"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"মুক্ত নেটৱৰ্ক"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"সুৰক্ষিত নেটৱৰ্ক"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index c675ce7..dfd1b7b 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi iki xətdir."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi üç xətdir."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi siqnalı tamdır."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Cihaza qoşuldu."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Açıq şəbəkə"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Təhlükəsiz şəbəkə"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index f7be60f..528e96e 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WiFi signal ima dve crte."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WiFi signal ima tri crte."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WiFi signal je najjači."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Povezano je sa uređajem."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Otvorena mreža"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Bezbedna mreža"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index d2f2851..52614b7 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Два слупкi Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Тры слупкi Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Поўны сігнал Wi-Fi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Устаноўлена падключэнне да прылады."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Адкрытая сетка"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Бяспечная сетка"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"АС Android"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 2540c12..b28ae67 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi е с две чертички."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi е с три чертички."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Сигналът за Wi-Fi е пълен."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Установена е връзка с устройството ви."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Отворена мрежа"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Защитена мрежа"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android (ОС)"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 745b944..c91f14c 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"ওয়াই ফাই এ দুইটি দণ্ড৷"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"ওয়াই ফাই এ তিনটি দণ্ড৷"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"ওয়াই ফাই এ সম্পূর্ণ সিগন্যাল৷"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"আপনার ডিভাইসের সাথে কানেক্ট করা হয়েছে।"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"খোলা নেটওয়ার্ক"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"সুরক্ষিত নেটওয়ার্ক"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index cab0841..aa1073d 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WiFi signal ima dvije crte."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WiFi signal ima tri crte."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WiFi signal je pun."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Povezani ste s uređajem."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Otvorena mreža"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sigurna mreža"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index a65b9c2..ab6393a7 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Senyal Wi-Fi: dues barres."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Senyal Wi-Fi: tres barres."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Senyal Wi-Fi: complet."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"S\'ha connectat al teu dispositiu."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Xarxa oberta"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Xarxa segura"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 96c02ae..e7d5243 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi – dvě čárky."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi – tři čárky."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi – plný signál."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Připojeno k vašemu zařízení."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Nezabezpečená síť"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Zabezpečená síť"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"OS Android"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 1e4c7b7..1612ac0 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi har to bjælker."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi har tre bjælker."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi har fuldt signal."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Der er oprettet forbindelse til din enhed."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Åbent netværk"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sikkert netværk"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 37f7e5a..73a44f1 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WLAN: zwei Balken"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WLAN: drei Balken"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WLAN: volle Signalstärke"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Mit deinem Gerät verbunden."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Offenes Netzwerk"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sicheres Netzwerk"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 84b8bf2..7dfd679 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Δύο γραμμές Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Τρεις γραμμές Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Άριστο σήμα Wi-Fi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Συνδέθηκε στη συσκευή σας."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Ανοικτό δίκτυο"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Ασφαλές δίκτυο"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Λειτουργικό σύστημα Android"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 7298c02..aead40d 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi two bars."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi three bars."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi signal full."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Connected to your device."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Open network"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Secure network"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 7298c02..aead40d 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi two bars."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi three bars."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi signal full."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Connected to your device."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Open network"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Secure network"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 7298c02..aead40d 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi two bars."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi three bars."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi signal full."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Connected to your device."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Open network"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Secure network"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 86541fd..291a68b 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Dos barras de Wi-Fi"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Tres barras de Wi-Fi"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Señal de Wi-Fi excelente"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Se estableció conexión con el dispositivo."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Red abierta"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Red segura"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"SO Android"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index ef9cc03..b4bc319 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Dos barras de Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Tres barras de Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Señal de Wi-Fi al máximo."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Conectado a tu dispositivo."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Red abierta"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Red segura"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"SO Android"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index c8d806b..e5cbaf6 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WiFi: kaks pulka."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WiFi: kolm pulka."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WiFi-signaal on tugev."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Seadmega ühendatud."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Avatud võrk"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Turvaline võrk"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-eu/arrays.xml b/packages/SettingsLib/res/values-eu/arrays.xml
index 95adf96..00f43a3 100644
--- a/packages/SettingsLib/res/values-eu/arrays.xml
+++ b/packages/SettingsLib/res/values-eu/arrays.xml
@@ -264,7 +264,7 @@
   <string-array name="debug_hw_overdraw_entries">
     <item msgid="1968128556747588800">"Desaktibatuta"</item>
     <item msgid="3033215374382962216">"Erakutsi gainidatzi diren eremuak"</item>
-    <item msgid="3474333938380896988">"Erakutsi daltonismorako eremuak"</item>
+    <item msgid="3474333938380896988">"Erakutsi deuteranomaliarako eremuak"</item>
   </string-array>
   <string-array name="app_process_limit_entries">
     <item msgid="794656271086646068">"Muga estandarra"</item>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 7d76514..90d45eb 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi sarearen bi barra."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi sarearen hiru barra."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi sarearen seinalea osoa."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Gailura konektatuta."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Sare irekia"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sare segurua"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android sistema eragilea"</string>
@@ -368,7 +367,7 @@
     <string name="debug_hw_overdraw" msgid="8944851091008756796">"Araztu GPU gainidazketa"</string>
     <string name="disable_overlays" msgid="4206590799671557143">"Desgaitu HW gainjartzeak"</string>
     <string name="disable_overlays_summary" msgid="1954852414363338166">"Erabili beti GPUa pantaila-muntaietarako"</string>
-    <string name="simulate_color_space" msgid="1206503300335835151">"Simulatu kolore-eremua"</string>
+    <string name="simulate_color_space" msgid="1206503300335835151">"Simulatu kolore-espazioa"</string>
     <string name="enable_opengl_traces_title" msgid="4638773318659125196">"Gaitu OpenGL aztarnak"</string>
     <string name="usb_audio_disable_routing" msgid="3367656923544254975">"Desgaitu USB bidez audioa bideratzeko aukera"</string>
     <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Desgaitu USB bidezko audio-gailuetara automatikoki bideratzeko aukera"</string>
@@ -441,7 +440,7 @@
     <string name="picture_color_mode_desc" msgid="151780973768136200">"Erabili sRGB"</string>
     <string name="daltonizer_mode_disabled" msgid="403424372812399228">"Desgaituta"</string>
     <string name="daltonizer_mode_monochromacy" msgid="362060873835885014">"Ikusmen-monokromia"</string>
-    <string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"Daltonismoa (gorri-berdeak)"</string>
+    <string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"Deuteranomalia (gorri-berdeak)"</string>
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanopia (gorri-berdeak)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (urdin-horia)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Koloreen zuzenketa"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index beb0b96..f3f97c4 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"‏دو نوار برای Wi‑Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"‏سه نوار برای Wi‑Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"‏قدرت سیگنال Wi‑Fi کامل است."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"به دستگاهتان متصل شد."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"شبکه باز"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"شبکه ایمن"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"‏سیستم‌عامل Android"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 8872316..7f3d0f2 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi-signaali – kaksi palkkia"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi-signaali – kolme palkkia"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Vahva Wi-Fi-signaali"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Yhdistetty laitteeseesi."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Avoin verkko"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Suojattu verkko"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android-käyttöjärjestelmä"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 98e3753..b7c3a81 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi : deux barres."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi : trois barres."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi : signal complet."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Connecté à votre appareil."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Réseau ouvert"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Réseau sécurisé"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Système d\'exploitation Android"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 4a64041..a9e0c6a 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Signal Wi-Fi moyen"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Signal Wi-Fi bon"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Signal Wi-Fi excellent"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Connecté à votre appareil."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Réseau ouvert"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Réseau sécurisé"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"OS Android"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 26738e6..09c2969 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Dúas barras de wifi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Tres barras de wifi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Sinal completo de wifi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Conexión establecida co teu dispositivo."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rede aberta"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rede segura"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"SO Android"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index b620d41..575c675 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi બે બાર."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi ત્રણ બાર."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"પૂર્ણ Wifi સિગ્નલ."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"તમારા ડિવાઇસ સાથે કનેક્ટેડ છે."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"નેટવર્ક ખોલો"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"સુરક્ષિત નેટવર્ક"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 3811167..655fac0 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"वाई-फ़ाई की दो पट्टी मिल रही हैं."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"वाई-फ़ाई की एक पट्टी मिल रही है."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"पूरे वाई-फ़ाई सिग्नल मिल रहे हैं."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"आपके डिवाइस से कनेक्ट हो गया है."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"खुला नेटवर्क"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"सुरक्षित नेटवर्क"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -598,7 +597,7 @@
     <string name="profile_info_settings_title" msgid="105699672534365099">"प्रोफ़ाइल की जानकारी"</string>
     <string name="user_need_lock_message" msgid="4311424336209509301">"इससे पहले कि आप कोई प्रतिबंधित प्रोफ़ाइल बनाएं, आपको अपने ऐप्लिकेशन  और व्यक्तिगत डेटा की सुरक्षा करने के लिए एक स्क्रीन लॉक सेट करना होगा."</string>
     <string name="user_set_lock_button" msgid="1427128184982594856">"लॉक सेट करें"</string>
-    <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> पर जाएं"</string>
+    <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> पर स्विच करें"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नए उपयोगकर्ता को जोड़ा जा रहा है…"</string>
     <string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"नया मेहमान खाता बनाया जा रहा है…"</string>
     <string name="add_user_failed" msgid="4809887794313944872">"नया उपयोगकर्ता जोड़ा नहीं जा सका"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 71a2dae..f0e16a4 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi signal ima dva stupca."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi signal ima tri stupca."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi signal je pun."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Povezano s vašim uređajem."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Otvorena mreža"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sigurna mreža"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -619,7 +618,7 @@
     <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Time će se izbrisati aplikacije i podaci iz trenutačne gostujuće sesije."</string>
     <string name="grant_admin" msgid="4323199171790522574">"Da, dodijeli status administratora"</string>
     <string name="not_grant_admin" msgid="3557849576157702485">"Ne, nemoj dodijeliti status administratora"</string>
-    <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Izlaz"</string>
+    <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Izađi"</string>
     <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Želite li spremiti aktivnosti gosta?"</string>
     <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Možete spremiti aktivnosti iz ove sesije ili izbrisati sve aplikacije i podatke"</string>
     <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Izbriši"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 5fac75d..420e0e9 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi-jel: két sáv."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi-jel: három sáv."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi-jel: teljes."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Csatlakoztatva az eszközhöz."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Nyílt hálózat"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Biztonságos hálózat"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index e40c5e1..705fcf6 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi-ի ուժգնությունը՝ երկու գիծ:"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi-ի ուժգնությունը՝ երեք գիծ:"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi-ի ազդանշանը ուժեղ է:"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Միացած է ձեր սարքին։"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Բաց ցանց"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Անվտանգ ցանց"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index ca1b6be..e33dd05 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi dua baris"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi tiga baris."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Sinyal Wi-Fi penuh."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Terhubung ke perangkat Anda."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Jaringan terbuka"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Jaringan aman"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"OS Android"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 1f2dcfb..e1fb248 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi: Tvö strik."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: Þrjú strik."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Fullur Wi-Fi sendistyrkur."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Tengt við tækið þitt."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Opið net"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Öruggt net"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android stýrikerfið"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index e255c85..b2cfd37 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi: due barre."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: tre barre."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Segnale Wi-Fi completo."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Connessione al dispositivo effettuata."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rete aperta"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rete protetta"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Sistema operativo Android"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index edcac10..9ca7477 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"‏שני פסים של Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"‏שלושה פסים של Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"‏אות Wi-Fi מלא."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"מחובר למכשיר שלך."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"רשת פתוחה"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"רשת מאובטחת"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index bf21de2..dcce2b4 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fiはレベル2です。"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fiはレベル3です。"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fiの電波はフルです。"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"デバイスに接続しました。"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"オープンネットワーク"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"保護されたネットワーク"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 9000de4..eb32e1c 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WiFi სიგნალი ორ ზოლზეა."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WiFi სიგნალი სამ ზოლზეა."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WiFi სიგნალი სრულია."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"დაკავშირებულია თქვენს მოწყობილობასთან."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ღია ქსელი"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"დაცული ქსელი"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 610fed1..73a8efd 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi сигналы — екі жолақ."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi сигналы — үш жолақ."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi сигналы толық."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Құрылғыңызға жалғанды."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Ашық желі"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Қауіпсіз желі"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index b7999c7..1c32e62 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi ពីរកាំ"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi បីកាំ"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"សេវា Wifi ពេញ"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"បានភ្ជាប់ទៅឧបករណ៍របស់អ្នក។"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"បើក​បណ្ដាញ"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"បណ្តាញ​ដែល​មានសុវត្ថិភាព"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"ប្រព័ន្ធ​ប្រតិបត្តិការ Android"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index c788dd5..ede347d 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"ವೈಫೈ ಎರಡು ಪಟ್ಟಿಗಳು."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"ವೈಫೈ ಮೂರು ಪಟ್ಟಿಗಳು."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"ವೈಫೈ ಸಿಗ್ನಲ್‌‌ ಪೂರ್ತಿ ಇದೆ."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"ನಿಮ್ಮ ಸಾಧನಕ್ಕೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ನೆಟ್‌ವರ್ಕ್‌ ತೆರೆಯಿರಿ"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"ಸುರಕ್ಷಿತ ನೆಟ್‌ವರ್ಕ್"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 2c51d1f..78bd616 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi 신호 막대가 두 개입니다."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi 신호 막대가 세 개입니다."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi 신호가 강합니다."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"기기에 연결되었습니다."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"개방형 네트워크"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"보안 네트워크"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 14814f4..a0b9123 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi: эки таякча."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi: үч таякча."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi: күчтүү сигнал."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Түзмөгүңүзгө туташты."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Ачык тармак"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Коопсуз тармак"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 6430a98..96b2dc1 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"ສັນຍານ Wi-Fi ສອງຂີດ."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi ສາມຂີດ."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"ສັນຍານ Wi-Fi ເຕັມ"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"ເຊື່ອມຕໍ່ກັບອຸປະກອນຂອງທ່ານແລ້ວ."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ເຄືອຂ່າຍເປີດ"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"ເຄືອຂ່າຍເຂົ້າລະຫັດ"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 3642a90..69630f4 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Dvi „Wi-Fi“ signalo juostos."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Trys „Wi-Fi“ signalo juostos."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Stiprus „Wi-Fi“ signalas."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Prisijungta prie įrenginio."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Atviras tinklas"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Saugus tinklas"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"„Android“ OS"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index c3163b8..4f76cee 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi: divas joslas"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: trīs joslas"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Pilna piekļuve Wi-Fi signālam"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Izveidots savienojums ar ierīci."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Atvērts tīkls"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Drošs tīkls"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 017fed7..1c80c65 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Две црти на Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Три црти на Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Полн сигнал на Wi-Fi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Поврзано со уредот."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Отворена мрежа"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Заклучена мрежа"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Оперативен систем Android"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 3ef3f63..31e3c83 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"വൈഫൈ സിഗ്നൽ രണ്ട് ബാറുകൾ."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"വൈഫൈ സിഗ്നൽ മൂന്ന് ബാറുകൾ."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"വൈഫൈ മികച്ച സിഗ്‌നൽ."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"നിങ്ങളുടെ ഉപകരണത്തിലേക്ക് കണക്റ്റ് ചെയ്തു."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ഓപ്പൺ നെറ്റ്‍വര്‍ക്ക്"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"സുരക്ഷിത നെറ്റ്‍വര്‍ക്ക്"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index ccd1d41..5a636d9 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi сүлжээний дохио хоёр баганатай байна."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi сүлжээний дохио гурван баганатай байна."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi-н дохио дүүрэн байна."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Таны төхөөрөмжид холбогдсон."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Нээлттэй сүлжээ"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Аюулгүй сүлжээ"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Андройд OS"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index a34a9d3..4d78e57 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"वाय-फाय दोन बार."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"वाय-फाय तीन बार."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"वाय-फाय सिग्नल संपूर्ण आहे."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"तुमच्या डिव्हाइसशी कनेक्ट केले आहे."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"नेटवर्क उघडा"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"सुरक्षित नेटवर्क"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index b7d2734..2ae69bd 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi dua bar."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi tiga bar."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Isyarat Wi-Fi penuh."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Disambungkan kepada peranti anda."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rangkaian terbuka"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rangkaian selamat"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"OS Android"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 0e43f65..7d6f2a7 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi  ၂ ဘား"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi  ၃ ဘား"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi  အပြည့်ရှိ"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"သင့်စက်သို့ ချိတ်ဆက်ထားသည်။"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"အများသုံး ကွန်ရက်"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"လုံခြုံသည့် ကွန်ရက်"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 43824c3..9e550fb 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi-signal med to stolper."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi-signal med tre stolper."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi-signalet er ved full styrke."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Koblet til enheten."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Åpent nettverk"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sikkert nettverk"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android-operativsystem"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index e879849..1f6ad5b 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi दुई पट्टि।"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi तीन बारहरू।"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"पूर्ण Wi-Fi सिंग्नल।"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"तपाईंको डिभाइसमा कनेक्ट गरिएको छ।"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"खुला नेटवर्क"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"सुरक्षित नेटवर्क"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index dedacff..6a6f184 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi: twee streepjes."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi: drie streepjes."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifii-signaal is op volledige sterkte."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Verbonden met je apparaat."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Open netwerk"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Beveiligd netwerk"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android-besturingssysteem"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index cbd9ffd..980a374 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"ୱାଇ-ଫାଇର ଦୁଇଟି ବାର୍‌ ଅଛି।"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"ୱାଇ-ଫାଇର ତିନୋଟି ବାର୍।"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"ୱାଇ-ଫାଇର ସଙ୍କେତ ସର୍ବୋଚ୍ଚ।"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"ଆପଣଙ୍କ ଡିଭାଇସ ସହ କନେକ୍ଟ କରାଯାଇଛି।"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ଖୋଲା ନେଟୱର୍କ"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"ସୁରକ୍ଷିତ ନେଟ୍‌ୱର୍କ"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index e4814ac..dd3fa15 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi ਦੋ ਬਾਰ।"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi ਤਿੰਨ ਬਾਰ।"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi ਸਿਗਨਲ ਪੂਰਾ।"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ।"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ਖੁੱਲ੍ਹਾ ਨੈੱਟਵਰਕ"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"ਸੁਰੱਖਿਅਤ ਨੈੱਟਵਰਕ"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index f5dc9cc..f4ebd29 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi: dwa paski."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: trzy paski."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi: pełna moc sygnału."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Połączono z urządzeniem."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Sieć otwarta"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Sieć zabezpieczona"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"System operacyjny Android"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 4e96bc6..bb6c7d8 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Duas barras de Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Três barras de Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Sinal Wi-Fi cheio."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Conectado ao dispositivo."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rede aberta"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rede segura"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Sistema operacional Android"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 0c4abdb..6bd5c2a 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Duas barras de Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Três barras de Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Sinal de Wi-Fi completo."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Com ligação ao dispositivo."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rede aberta"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rede segura"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"SO Android"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 4e96bc6..bb6c7d8 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Duas barras de Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Três barras de Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Sinal Wi-Fi cheio."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Conectado ao dispositivo."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rede aberta"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rede segura"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Sistema operacional Android"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 529a6c6..f4399dc 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Semnal Wi-Fi: două bare."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Semnal Wi-Fi: trei bare."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Semnal Wi-Fi: complet."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Conectată la dispozitiv."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rețea nesecurizată"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Securizează rețeaua"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Sistem de operare Android"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 87a81ee..5d45e29 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi: два деления"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: три деления"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi: надежный сигнал"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Подключено к точке доступа на вашем устройстве."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Открытая сеть"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Защищенная сеть"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"ОС Android"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 9015a42..996507d 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi තීරු දෙකයි."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WiFi තීරු තුනයි."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi සංඥාව පිරී ඇත."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"ඔබේ උපාංගයට සම්බන්ධයි."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"විවෘත ජාලය"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"ආරක්ෂිත ජාලය"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 8c6bf4f..a51acd4 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Dve čiarky signálu Wi‑Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Tri čiarky signálu Wi‑Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Plný signál Wi‑Fi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Pripojené k vášmu zariadeniu."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Otvorená sieť"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Zabezpečená sieť"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"OS Android"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index cc19abe..0d7188d 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Dve črtici signala Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Tri črtice signala Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Poln signal Wi-Fi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Povezava z napravo je vzpostavljena."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Odprto omrežje"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Varno omrežje"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"OS Android"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 43a2c4c..c2bcce7 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi ka dy vija."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: tre vija."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi ka sinjal të plotë."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Lidhur me pajisjen tënde"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rrjet i hapur"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rrjet i sigurt"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Sistemi operativ Android"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 00a98bc..09ff994 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WiFi сигнал има две црте."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WiFi сигнал има три црте."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WiFi сигнал је најјачи."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Повезано је са уређајем."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Отворена мрежа"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Безбедна мрежа"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android ОС"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 01b462d..fdd9d8c 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi: två staplar."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi: tre staplar."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Full signalstyrka för wifi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Ansluten till enheten."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Öppet nätverk"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Säkert nätverk"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Operativsystemet Android"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 9453e5f..bd41752 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Vipima mtandao viwili vya Wifi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Vipima mtandao vitatu vya Wifi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Nguvu kamili ya mtandao wa Wifi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Imeunganishwa kwenye kifaa chako."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Mtandao unaotumiwa na mtu yeyote"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Mtandao salama"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Mfumo wa Uendeshaji wa Android"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 836b4da..fd379f0 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"வைஃபை சிக்னல்: இரண்டு கோடுகள்."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"வைஃபை சிக்னல்: மூன்று கோடுகள்."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"வைஃபை சிக்னல் முழுமையாக உள்ளது."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"உங்கள் சாதனத்துடன் இணைக்கப்பட்டது."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"கடவுச்சொல் தேவைப்படாத திறந்த நெட்வொர்க்"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"கடவுச்சொல் தேவைப்படும் பாதுகாப்பான நெட்வொர்க்"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 34b8264..3e9d8be 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi సిగ్నల్ రెండు బార్‌లు ఉంది."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi సిగ్నల్ మూడు బార్‌లు ఉంది."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi సిగ్నల్ పూర్తిగా ఉంది."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"మీ పరికరానికి కనెక్ట్ చేయబడింది."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"ఓపెన్ నెట్‌వర్క్"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"సురక్షిత నెట్‌వర్క్"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index da3a5c5..e67f371 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"สัญญาณ Wi-Fi 2 ขีด"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"สัญญาณ Wi-Fi 3 ขีด"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"สัญญาณ Wi-Fi เต็ม"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"เชื่อมต่อกับอุปกรณ์แล้ว"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"เครือข่ายแบบเปิด"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"เครือข่ายที่ปลอดภัย"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"ระบบปฏิบัติการ Android"</string>
@@ -598,7 +597,7 @@
     <string name="profile_info_settings_title" msgid="105699672534365099">"ข้อมูลโปรไฟล์"</string>
     <string name="user_need_lock_message" msgid="4311424336209509301">"ก่อนที่คุณจะสามารถสร้างโปรไฟล์ที่ถูกจำกัดได้ คุณจะต้องตั้งค่าล็อกหน้าจอเพื่อปกป้องแอปและข้อมูลส่วนตัวของคุณ"</string>
     <string name="user_set_lock_button" msgid="1427128184982594856">"ตั้งค่าล็อก"</string>
-    <string name="user_switch_to_user" msgid="6975428297154968543">"เปลี่ยนเป็น <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+    <string name="user_switch_to_user" msgid="6975428297154968543">"เปลี่ยนเป็น<xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"กำลังสร้างผู้ใช้ใหม่…"</string>
     <string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"กำลังสร้างผู้ใช้ชั่วคราวใหม่…"</string>
     <string name="add_user_failed" msgid="4809887794313944872">"สร้างผู้ใช้ใหม่ไม่ได้"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index f6d2256..440bbe7 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"May dalawang bar ang Wifi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"May tatlong bar ang Wifi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Puno ang signal ng Wifi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Nakakonekta sa iyong device."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Bukas na network"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Ligtas na network"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index acd3d00e..b38012f 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Kablosuz sinyal gücü iki çubuk."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Kablosuz sinyal gücü üç çubuk."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Kablosuz sinyal gücü tam."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Cihazınıza bağlandı."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Açık ağ"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Güvenli ağ"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index dd5898a..4f08547 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Дві смужки сигналу Wi-Fi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Три смужки сигналу Wi-Fi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Максимальний сигнал Wi-Fi."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Підключено до пристрою."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Відкрита мережа"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Захищена мережа"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"ОС Android"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 182bd04..ce67d15 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"‏Wifi دو بارز۔"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"‏Wifi تین بارز۔"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"‏Wifi سگنل پورا ہے۔"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"آپ کے آلے سے منسلک ہے۔"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"اوپن نیٹ ورک"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"محفوظ نیٹ ورک"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index b831a4b..77da981 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi: ikkita ustun"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi: uchta ustun"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi: signal to‘liq"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Qurilmaga ulandi."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Ochiq tarmoq"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Xavfsiz tarmoq"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 3e94593..bf510f6 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Tín hiệu Wi-Fi hai vạch."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Tín hiệu Wi-Fi ba vạch."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Tín hiệu Wi-Fi đủ."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Đã kết nối với thiết bị của bạn."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Mạng mở"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Mạng bảo mật"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Hệ điều hành Android"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 53389c7..8e3145a 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"WLAN 信号强度为两格。"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"WLAN 信号强度为三格。"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"WLAN 信号满格。"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"已连接到您的设备。"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"开放网络"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"安全网络"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android 操作系统"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index daedba6..aa9f21f 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi 訊號兩格。"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi 訊號三格。"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi 訊號滿格。"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"已連接裝置。"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"開放式網絡"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"安全網絡"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android 作業系統"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 94ebd98..3c65a4d 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi 訊號強度兩格。"</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi 訊號強度三格。"</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi 訊號強度滿格。"</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"已連上裝置。"</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"開放式網路"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"安全網路"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android 作業系統"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 92a4b81..08b04cc 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -157,8 +157,7 @@
     <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Amabha amabili we-Wifi."</string>
     <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Amabha amathathu we-Wifi."</string>
     <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Isiginali ye-Wifi igcwele."</string>
-    <!-- no translation found for accessibility_wifi_other_device (2815627624555795918) -->
-    <skip />
+    <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Ixhume kudivayisi yakho."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Vula inethiwekhi"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Inethiwekhi evikelekile"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"I-Android OS"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 49bd9d9..9687674 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -518,6 +518,8 @@
     <string name="category_personal">Personal</string>
     <!-- Header for items under the work user [CHAR LIMIT=30] -->
     <string name="category_work">Work</string>
+    <!-- Header for items under the private profile user [CHAR LIMIT=30] -->
+    <string name="category_private">Private</string>
     <!-- Header for items under the clone user [CHAR LIMIT=30] -->
     <string name="category_clone">Clone</string>
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 412a342..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;
@@ -67,8 +68,7 @@
     static final String STORAGE_MANAGER_ENABLED_PROPERTY =
             "ro.storage_manager.enabled";
 
-    @VisibleForTesting
-    static final String INCOMPATIBLE_CHARGER_WARNING_DISABLED =
+    public static final String INCOMPATIBLE_CHARGER_WARNING_DISABLED =
             "incompatible_charger_warning_disabled";
 
     private static Signature[] sSystemSignature;
@@ -598,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/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java
deleted file mode 100644
index a78440c..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.deviceinfo;
-
-import android.content.Context;
-import android.os.UserManager;
-
-import com.android.settingslib.Utils;
-import com.android.settingslib.core.AbstractPreferenceController;
-
-public abstract class AbstractSimStatusImeiInfoPreferenceController
-        extends AbstractPreferenceController {
-    public AbstractSimStatusImeiInfoPreferenceController(Context context) {
-        super(context);
-    }
-
-    @Override
-    public boolean isAvailable() {
-        return mContext.getSystemService(UserManager.class).isAdminUser()
-                && !Utils.isWifiOnly(mContext);
-    }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 7f1f3f6..2032328 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -110,7 +110,8 @@
     }
 
     /**
-     * Determine whether the device is plugged in wireless. */
+     * Determine whether the device is plugged in wireless.
+     */
     public boolean isPluggedInWireless() {
         return plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
     }
@@ -185,6 +186,22 @@
         return status == BATTERY_STATUS_FULL || level >= 100;
     }
 
+    /**
+     * Whether or not the device is charged. Note that some devices never return 100% for battery
+     * level, so this allows either battery level or status to determine if the battery is charged.
+     *
+     * @param status the value from extra {@link BatteryManager.EXTRA_STATUS} of
+     *     {@link Intent.ACTION_BATTERY_CHANGED} intent
+     * @param level the value from extra {@link BatteryManager.EXTRA_LEVEL} of
+     *     {@link Intent.ACTION_BATTERY_CHANGED} intent
+     * @param scale the value from extra {@link BatteryManager.EXTRA_SCALE} of
+     *     {@link Intent.ACTION_BATTERY_CHANGED} intent
+     */
+    public static boolean isCharged(int status, int level, int scale) {
+        var batteryLevel = getBatteryLevel(level, scale);
+        return isCharged(status, batteryLevel);
+    }
+
     /** Gets the battery level from the intent. */
     public static int getBatteryLevel(Intent batteryChangedIntent) {
         if (batteryChangedIntent == null) {
@@ -193,6 +210,14 @@
         final int level =
                 batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, BATTERY_LEVEL_UNKNOWN);
         final int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
+        return getBatteryLevel(level, scale);
+    }
+
+    /**
+     * Gets the battery level from the value of {@link Intent.BATTERY_CHANGED_INTENT}'s EXTRA_LEVEL
+     * and EXTRA_SCALE.
+     */
+    public static int getBatteryLevel(int level, int scale) {
         return scale == 0
                 ? BATTERY_LEVEL_UNKNOWN
                 : Math.round((level / (float) scale) * 100f);
@@ -253,11 +278,22 @@
      *
      * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
      * @return {@code true} if the battery level is less or equal to {@link
-     *     SEVERE_LOW_BATTERY_THRESHOLD}
+     * SEVERE_LOW_BATTERY_THRESHOLD}
      */
     public static boolean isSevereLowBattery(Intent batteryChangedIntent) {
-        int level = getBatteryLevel(batteryChangedIntent);
-        return level <= SEVERE_LOW_BATTERY_THRESHOLD;
+        int batteryLevel = getBatteryLevel(batteryChangedIntent);
+        return isSevereLowBattery(batteryLevel);
+    }
+
+    /**
+     * Whether the battery is severe low or not.
+     *
+     * @param batteryLevel the value of battery level
+     * @return {@code true} if the battery level is less or equal to {@link
+     * SEVERE_LOW_BATTERY_THRESHOLD}
+     */
+    public static boolean isSevereLowBattery(int batteryLevel) {
+        return batteryLevel <= SEVERE_LOW_BATTERY_THRESHOLD;
     }
 
     /**
@@ -265,11 +301,21 @@
      *
      * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
      * @return {@code true} if the battery level is less or equal to {@link
-     *     EXTREME_LOW_BATTERY_THRESHOLD}
+     * EXTREME_LOW_BATTERY_THRESHOLD}
      */
     public static boolean isExtremeLowBattery(Intent batteryChangedIntent) {
         int level = getBatteryLevel(batteryChangedIntent);
-        return level <= EXTREME_LOW_BATTERY_THRESHOLD;
+        return isExtremeLowBattery(level);
+    }
+
+    /**
+     * Whether the battery is extreme low or not.
+     *
+     * @return {@code true} if the {@code batteryLevel} is less or equal to
+     * {@link EXTREME_LOW_BATTERY_THRESHOLD}
+     */
+    public static boolean isExtremeLowBattery(int batteryLevel) {
+        return batteryLevel <= EXTREME_LOW_BATTERY_THRESHOLD;
     }
 
     /**
@@ -277,7 +323,7 @@
      *
      * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
      * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell
-     *     defend, or temp defend
+     * defend, or temp defend
      */
     public static boolean isBatteryDefender(Intent batteryChangedIntent) {
         int chargingStatus =
@@ -298,9 +344,8 @@
     }
 
     /**
-     * Gets the max charging current and max charging voltage form {@link
-     * Intent.ACTION_BATTERY_CHANGED} and calculates the charging speed based on the {@link
-     * R.integer.config_chargingSlowlyThreshold} and {@link R.integer.config_chargingFastThreshold}.
+     * Calculates the charging speed based on the {@link R.integer.config_chargingSlowlyThreshold}
+     * and {@link R.integer.config_chargingFastThreshold}.
      *
      * @param context the application context
      * @param batteryChangedIntent the intent from {@link Intent.ACTION_BATTERY_CHANGED}
@@ -308,7 +353,29 @@
      *     CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN}
      */
     public static int getChargingSpeed(Context context, Intent batteryChangedIntent) {
-        final int maxChargingMicroWatt = calculateMaxChargingMicroWatt(batteryChangedIntent);
+        final int maxChargingMicroCurrent =
+                batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1);
+        int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
+
+        return calculateChargingSpeed(context, maxChargingMicroCurrent, maxChargingMicroVolt);
+    }
+
+    /**
+     * Calculates the charging speed based on the {@link R.integer.config_chargingSlowlyThreshold}
+     * and {@link R.integer.config_chargingFastThreshold}.
+     *
+     * @param maxChargingMicroCurrent the max charging micro current that is retrieved form the
+     *     extra of {@link Intent.Action_BATTERY_CHANGED}
+     * @param maxChargingMicroVolt the max charging micro voltage that is retrieved form the extra
+     *     of {@link Intent.Action_BATTERY_CHANGED}
+     * @return the charging speed. {@link CHARGING_REGULAR}, {@link CHARGING_FAST}, {@link
+     *     CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN}
+     */
+    public static int calculateChargingSpeed(
+            Context context, int maxChargingMicroCurrent, int maxChargingMicroVolt) {
+        final int maxChargingMicroWatt =
+                calculateMaxChargingMicroWatt(maxChargingMicroCurrent, maxChargingMicroVolt);
+
         if (maxChargingMicroWatt <= 0) {
             return CHARGING_UNKNOWN;
         } else if (maxChargingMicroWatt
@@ -326,6 +393,12 @@
         final int maxChargingMicroAmp =
                 batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1);
         int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
+
+        return calculateMaxChargingMicroWatt(maxChargingMicroAmp, maxChargingMicroVolt);
+    }
+
+    private static int calculateMaxChargingMicroWatt(int maxChargingMicroAmp,
+            int maxChargingMicroVolt) {
         if (maxChargingMicroVolt <= 0) {
             maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 41afc7b..a63bbdf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -26,6 +26,7 @@
 
 import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.media.MediaRoute2Info;
@@ -51,6 +52,34 @@
 
     private final DeviceIconUtil mDeviceIconUtil;
 
+    /** Returns the device name for the given {@code routeInfo}. */
+    public static String getSystemRouteNameFromType(
+            @NonNull Context context, @NonNull MediaRoute2Info routeInfo) {
+        CharSequence name;
+        switch (routeInfo.getType()) {
+            case TYPE_WIRED_HEADSET:
+            case TYPE_WIRED_HEADPHONES:
+            case TYPE_USB_DEVICE:
+            case TYPE_USB_HEADSET:
+            case TYPE_USB_ACCESSORY:
+                name = context.getString(R.string.media_transfer_wired_usb_device_name);
+                break;
+            case TYPE_DOCK:
+                name = context.getString(R.string.media_transfer_dock_speaker_device_name);
+                break;
+            case TYPE_BUILTIN_SPEAKER:
+                name = context.getString(R.string.media_transfer_this_device_name);
+                break;
+            case TYPE_HDMI:
+                name = context.getString(R.string.media_transfer_external_device_name);
+                break;
+            default:
+                name = context.getString(R.string.media_transfer_default_device_name);
+                break;
+        }
+        return name.toString();
+    }
+
     PhoneMediaDevice(Context context, MediaRoute2Info info, String packageName) {
         this(context, info, packageName, null);
     }
@@ -69,29 +98,7 @@
     @SuppressWarnings("NewApi")
     @Override
     public String getName() {
-        CharSequence name;
-        switch (mRouteInfo.getType()) {
-            case TYPE_WIRED_HEADSET:
-            case TYPE_WIRED_HEADPHONES:
-            case TYPE_USB_DEVICE:
-            case TYPE_USB_HEADSET:
-            case TYPE_USB_ACCESSORY:
-                name = mContext.getString(R.string.media_transfer_wired_usb_device_name);
-                break;
-            case TYPE_DOCK:
-                name = mContext.getString(R.string.media_transfer_dock_speaker_device_name);
-                break;
-            case TYPE_BUILTIN_SPEAKER:
-                name = mContext.getString(R.string.media_transfer_this_device_name);
-                break;
-            case TYPE_HDMI:
-                name = mContext.getString(R.string.media_transfer_external_device_name);
-                break;
-            default:
-                name = mContext.getString(R.string.media_transfer_default_device_name);
-                break;
-        }
-        return name.toString();
+        return getSystemRouteNameFromType(mContext, mRouteInfo);
     }
 
     @Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java
index 2b1e808..507dcbc 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java
@@ -55,8 +55,7 @@
     @Test
     public void onCreate_userAddedChildViewsBeMovedToContentFrame() {
         CollapsingCoordinatorLayout layout = mActivity.getCollapsingCoordinatorLayout();
-        View contentFrameView =
-                layout.findViewById(com.android.settingslib.widget.R.id.content_frame);
+        View contentFrameView = layout.findViewById(R.id.content_frame);
 
         TextView textView = contentFrameView.findViewById(com.android.settingslib.robotests.R.id.text_hello_world);
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java
deleted file mode 100644
index 52d243a..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.deviceinfo;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.robolectric.shadow.api.Shadow.extract;
-
-import android.os.UserManager;
-import android.telephony.TelephonyManager;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {SimStatusImeiInfoPreferenceControllerTest.ShadowUserManager.class,
-                SimStatusImeiInfoPreferenceControllerTest.ShadowTelephonyManager.class})
-public class SimStatusImeiInfoPreferenceControllerTest {
-
-    private AbstractSimStatusImeiInfoPreferenceController mController;
-
-    @Before
-    public void setUp() {
-        mController = new AbstractSimStatusImeiInfoPreferenceController(
-                RuntimeEnvironment.application) {
-            @Override
-            public String getPreferenceKey() {
-                return null;
-            }
-        };
-    }
-
-    @Test
-    public void testIsAvailable_isAdminAndHasMobile_shouldReturnTrue() {
-        ShadowUserManager userManager =
-                extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
-        userManager.setIsAdminUser(true);
-        ShadowTelephonyManager telephonyManager =
-                extract(RuntimeEnvironment.application.getSystemService(TelephonyManager.class));
-        telephonyManager.setDataCapable(true);
-
-        assertThat(mController.isAvailable()).isTrue();
-    }
-
-    @Test
-    public void testIsAvailable_isAdminButNoMobile_shouldReturnFalse() {
-        ShadowUserManager userManager =
-                extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
-        userManager.setIsAdminUser(true);
-        ShadowTelephonyManager telephonyManager =
-                extract(RuntimeEnvironment.application.getSystemService(TelephonyManager.class));
-        telephonyManager.setDataCapable(false);
-
-        assertThat(mController.isAvailable()).isFalse();
-    }
-
-    @Test
-    public void testIsAvailable_isNotAdmin_shouldReturnFalse() {
-        ShadowUserManager userManager =
-                extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
-        userManager.setIsAdminUser(false);
-
-        assertThat(mController.isAvailable()).isFalse();
-    }
-
-    @Implements(UserManager.class)
-    public static class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager {
-
-        private boolean mAdminUser;
-
-        public void setIsAdminUser(boolean isAdminUser) {
-            mAdminUser = isAdminUser;
-        }
-
-        @Implementation
-        public boolean isAdminUser() {
-            return mAdminUser;
-        }
-    }
-
-    @Implements(TelephonyManager.class)
-    public static class ShadowTelephonyManager
-            extends org.robolectric.shadows.ShadowTelephonyManager {
-        private boolean mDataCapable = false;
-        private void setDataCapable(boolean capable) {
-            mDataCapable = capable;
-        }
-
-        @Implementation
-        public boolean isDataCapable() {
-            return mDataCapable;
-        }
-    }
-}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java
index 6195d75..71545b7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java
@@ -36,10 +36,10 @@
 import android.graphics.drawable.ShapeDrawable;
 import android.os.Bundle;
 
-import com.android.settingslib.widget.adaptiveicon.R;
 import com.android.settingslib.drawer.ActivityTile;
 import com.android.settingslib.drawer.CategoryKey;
 import com.android.settingslib.drawer.Tile;
+import com.android.settingslib.widget.adaptiveicon.R;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -105,15 +105,15 @@
 
         icon.setBackgroundColor(mContext, tile);
 
-        assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor(
-                com.android.settingslib.widget.R.color.homepage_generic_icon_background));
+        assertThat(icon.mBackgroundColor).isEqualTo(
+                mContext.getColor(R.color.homepage_generic_icon_background));
     }
 
     @Test
     public void onBindTile_externalTileWithBackgroundColorHint_shouldUpdateIcon() {
         final Tile tile = spy(new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE));
         mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT,
-                com.android.settingslib.widget.R.color.bt_outline_color);
+                R.color.bt_outline_color);
         doReturn(Icon.createWithResource(mContext, com.android.settingslib.R.drawable.ic_system_update))
                 .when(tile).getIcon(mContext);
 
@@ -121,8 +121,7 @@
                 new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK));
         icon.setBackgroundColor(mContext, tile);
 
-        assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor(
-                com.android.settingslib.widget.R.color.bt_outline_color));
+        assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor(R.color.bt_outline_color));
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
index ccbe4f0..0ce83c6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
@@ -58,16 +58,14 @@
     @Test
     public void setLearnMoreText_shouldSetAsTextInLearnMore() {
         final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(
-                LayoutInflater.from(mContext)
-                        .inflate(com.android.settingslib.widget.R.layout.preference_footer, null));
+                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null));
         mFooterPreference.setLearnMoreText("Custom learn more");
         mFooterPreference.setLearnMoreAction(view -> { /* do nothing */ } /* listener */);
 
         mFooterPreference.onBindViewHolder(holder);
 
-        assertThat(((TextView) holder.findViewById(
-                com.android.settingslib.widget.R.id.settingslib_learn_more)).getText().toString())
-                .isEqualTo("Custom learn more");
+        TextView learnMoreView = (TextView) holder.findViewById(R.id.settingslib_learn_more);
+        assertThat(learnMoreView.getText().toString()).isEqualTo("Custom learn more");
     }
 
     @Test
@@ -95,8 +93,7 @@
     @Test
     public void onBindViewHolder_whenTitleIsNull_shouldNotRaiseNpe() {
         PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
-                LayoutInflater.from(mContext)
-                        .inflate(R.layout.preference_footer, null)));
+                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
         when(viewHolder.findViewById(androidx.core.R.id.title)).thenReturn(null);
 
         Throwable actualThrowable = null;
@@ -112,10 +109,8 @@
     @Test
     public void onBindViewHolder_whenLearnMoreIsNull_shouldNotRaiseNpe() {
         PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
-                LayoutInflater.from(mContext)
-                        .inflate(com.android.settingslib.widget.R.layout.preference_footer, null)));
-        when(viewHolder.findViewById(com.android.settingslib.widget.R.id.settingslib_learn_more))
-                .thenReturn(null);
+                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
+        when(viewHolder.findViewById(R.id.settingslib_learn_more)).thenReturn(null);
 
         Throwable actualThrowable = null;
         try {
@@ -130,8 +125,7 @@
     @Test
     public void onBindViewHolder_whenIconFrameIsNull_shouldNotRaiseNpe() {
         PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
-                LayoutInflater.from(mContext)
-                        .inflate(com.android.settingslib.widget.R.layout.preference_footer, null)));
+                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
         when(viewHolder.findViewById(R.id.icon_frame)).thenReturn(null);
 
         Throwable actualThrowable = null;
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
deleted file mode 100644
index 0b9ba8d..0000000
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open 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.settingslib.testutils.shadow;
-
-import static android.os.Build.VERSION_CODES.O;
-
-import android.app.ActivityManager;
-import android.app.IActivityManager;
-
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.Resetter;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.util.ReflectionHelpers;
-
-@Implements(ActivityManager.class)
-public class ShadowActivityManager {
-    private static int sCurrentUserId = 0;
-    private static int sUserSwitchedTo = -1;
-
-    @Resetter
-    public static void reset() {
-        sCurrentUserId = 0;
-        sUserSwitchedTo = 0;
-    }
-
-    @Implementation
-    protected static int getCurrentUser() {
-        return sCurrentUserId;
-    }
-
-    @Implementation
-    protected boolean switchUser(int userId) {
-        sUserSwitchedTo = userId;
-        return true;
-    }
-
-    @Implementation(minSdk = O)
-    protected static IActivityManager getService() {
-        return ReflectionHelpers.createNullProxy(IActivityManager.class);
-    }
-
-    public boolean getSwitchUserCalled() {
-        return sUserSwitchedTo != -1;
-    }
-
-    public int getUserSwitchedTo() {
-        return sUserSwitchedTo;
-    }
-
-    public static void setCurrentUser(int userId) {
-        sCurrentUserId = userId;
-    }
-
-    public static ShadowActivityManager getShadow() {
-        return (ShadowActivityManager) Shadow.extract(
-                RuntimeEnvironment.application.getSystemService(ActivityManager.class));
-    }
-}
diff --git a/packages/SettingsProvider/OWNERS b/packages/SettingsProvider/OWNERS
index 5ade971..86ae581 100644
--- a/packages/SettingsProvider/OWNERS
+++ b/packages/SettingsProvider/OWNERS
@@ -1,5 +1 @@
-hackbod@android.com
-hackbod@google.com
-narayan@google.com
-svetoslavganov@google.com
 include /PACKAGE_MANAGER_OWNERS
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 1d25ac7..e5dbe5f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -69,6 +69,7 @@
         Settings.Global.PRIVATE_DNS_SPECIFIER,
         Settings.Global.SOFT_AP_TIMEOUT_ENABLED,
         Settings.Global.ZEN_DURATION,
+        Settings.Global.REVERSE_CHARGING_AUTO_ON,
         Settings.Global.CHARGING_VIBRATION_ENABLED,
         Settings.Global.AWARE_ALLOWED,
         Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP,                   // moved to secure
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index ba4ad36..c6e9c03 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -143,6 +143,7 @@
         Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
         Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED,
         Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
+        Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT,
         Settings.Secure.VOLUME_HUSH_GESTURE,
         Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT,
         Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
@@ -245,6 +246,9 @@
         Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
         Settings.Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED,
         Settings.Secure.SEARCH_LONG_PRESS_HOME_ENABLED,
-        Settings.Secure.HUB_MODE_TUTORIAL_STATE
+        Settings.Secure.HUB_MODE_TUTORIAL_STATE,
+        Settings.Secure.STYLUS_BUTTONS_ENABLED,
+        Settings.Secure.STYLUS_HANDWRITING_ENABLED,
+        Settings.Secure.DEFAULT_NOTE_TASK_PROFILE
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 248c60c..fe39c4f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -101,5 +101,10 @@
         Settings.System.CAMERA_FLASH_NOTIFICATION,
         Settings.System.SCREEN_FLASH_NOTIFICATION,
         Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
+        Settings.System.PEAK_REFRESH_RATE,
+        Settings.System.MIN_REFRESH_RATE,
+        Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
+        Settings.System.NOTIFICATION_COOLDOWN_ALL,
+        Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index ba06185..7e8fe7e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -171,6 +171,7 @@
         VALIDATORS.put(Global.WIFI_SCAN_THROTTLE_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.APP_AUTO_RESTRICTION_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.ZEN_DURATION, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(Global.REVERSE_CHARGING_AUTO_ON, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.CHARGING_VIBRATION_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.REQUIRE_PASSWORD_TO_DECRYPT, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 19fde75..0727677 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -212,6 +212,7 @@
         VALIDATORS.put(Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.VOLUME_HUSH_GESTURE, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(
                 Secure.ENABLED_NOTIFICATION_LISTENERS,
@@ -394,5 +395,9 @@
                 BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.DND_CONFIGS_MIGRATED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.HUB_MODE_TUTORIAL_STATE, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.STYLUS_BUTTONS_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.STYLUS_HANDWRITING_ENABLED,
+                new DiscreteValueValidator(new String[] {"-1", "0", "1"}));
+        VALIDATORS.put(Secure.DEFAULT_NOTE_TASK_PROFILE, NON_NEGATIVE_INTEGER_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 29f27f7..eba74ab 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -21,6 +21,7 @@
 import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.LENIENT_IP_ADDRESS_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_FLOAT_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.URI_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.VIBRATION_INTENSITY_VALIDATOR;
@@ -30,6 +31,7 @@
 import android.content.ComponentName;
 import android.hardware.display.ColorDisplayManager;
 import android.os.BatteryManager;
+import android.provider.Settings.Global;
 import android.provider.Settings.System;
 import android.util.ArrayMap;
 
@@ -236,5 +238,10 @@
         VALIDATORS.put(System.CAMERA_FLASH_NOTIFICATION, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION_COLOR, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(System.PEAK_REFRESH_RATE, NON_NEGATIVE_FLOAT_VALIDATOR);
+        VALIDATORS.put(System.MIN_REFRESH_RATE, NON_NEGATIVE_FLOAT_VALIDATOR);
+        VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ALL, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 34d3d44..46cd725 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -121,6 +121,7 @@
 import com.android.internal.accessibility.util.AccessibilityUtils;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.PackageMonitor;
+import com.android.internal.display.RefreshRateSettingsUtils;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.providers.settings.SettingsState.Setting;
@@ -3878,7 +3879,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 222;
+            private static final int SETTINGS_VERSION = 223;
 
             private final int mUserId;
 
@@ -5935,10 +5936,6 @@
 
                 if (currentVersion == 218) {
                     // Version 219: Removed
-                    // TODO(b/211737588): Back up the Smooth Display setting
-                    // Future upgrades to the `peak_refresh_rate` and `min_refresh_rate` settings
-                    // should account for the database in a non-upgraded and upgraded (change id:
-                    // Ib2cb2dd100f06f5452083b7606109a486e795a0e) state.
                     currentVersion = 219;
                 }
 
@@ -6004,6 +6001,56 @@
                     currentVersion = 222;
                 }
 
+                // Version 222: Set peak refresh rate and min refresh rate to infinity if it's
+                // meant to be the highest possible refresh rate. This is needed so that we can
+                // back up and restore those settings on other devices. Other devices might have
+                // different highest possible refresh rates.
+                if (currentVersion == 222) {
+                    final SettingsState systemSettings = getSystemSettingsLocked(userId);
+                    final Setting peakRefreshRateSetting =
+                            systemSettings.getSettingLocked(Settings.System.PEAK_REFRESH_RATE);
+                    final Setting minRefreshRateSetting =
+                            systemSettings.getSettingLocked(Settings.System.MIN_REFRESH_RATE);
+                    float highestRefreshRate = RefreshRateSettingsUtils
+                            .findHighestRefreshRateForDefaultDisplay(getContext());
+
+                    if (!peakRefreshRateSetting.isNull()) {
+                        try {
+                            float peakRefreshRate =
+                                    Float.parseFloat(peakRefreshRateSetting.getValue());
+                            if (Math.round(peakRefreshRate) == Math.round(highestRefreshRate)) {
+                                systemSettings.insertSettingLocked(
+                                        Settings.System.PEAK_REFRESH_RATE,
+                                        String.valueOf(Float.POSITIVE_INFINITY),
+                                        /* tag= */ null,
+                                        /* makeDefault= */ false,
+                                        SettingsState.SYSTEM_PACKAGE_NAME);
+                            }
+                        } catch (NumberFormatException e) {
+                            // Do nothing. Leave the value as is.
+                        }
+                    }
+
+                    if (!minRefreshRateSetting.isNull()) {
+                        try {
+                            float minRefreshRate =
+                                    Float.parseFloat(minRefreshRateSetting.getValue());
+                            if (Math.round(minRefreshRate) == Math.round(highestRefreshRate)) {
+                                systemSettings.insertSettingLocked(
+                                        Settings.System.MIN_REFRESH_RATE,
+                                        String.valueOf(Float.POSITIVE_INFINITY),
+                                        /* tag= */ null,
+                                        /* makeDefault= */ false,
+                                        SettingsState.SYSTEM_PACKAGE_NAME);
+                            }
+                        } catch (NumberFormatException e) {
+                            // Do nothing. Leave the value as is.
+                        }
+                    }
+
+                    currentVersion = 223;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 9ddc976a..7bca944 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -98,8 +98,6 @@
                     Settings.System.VOLUME_VOICE, // deprecated since API 2?
                     Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
                     Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
-                    Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities
-                    Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities
                     Settings.System.SCREEN_BRIGHTNESS_FLOAT,
                     Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
                     Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE,
@@ -735,7 +733,6 @@
                  Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
                  Settings.Secure.CONTENT_CAPTURE_ENABLED,
                  Settings.Secure.DEFAULT_INPUT_METHOD,
-                 Settings.Secure.DEFAULT_NOTE_TASK_PROFILE,
                  Settings.Secure.DEVICE_PAIRED,
                  Settings.Secure.DIALER_DEFAULT_APPLICATION,
                  Settings.Secure.DISABLED_PRINT_SERVICES,
@@ -805,8 +802,6 @@
                  Settings.Secure.SLEEP_TIMEOUT,
                  Settings.Secure.SMS_DEFAULT_APPLICATION,
                  Settings.Secure.SPELL_CHECKER_ENABLED,  // Intentionally removed in Q
-                 Settings.Secure.STYLUS_BUTTONS_ENABLED,
-                 Settings.Secure.STYLUS_HANDWRITING_ENABLED,
                  Settings.Secure.TRUST_AGENTS_INITIALIZED,
                  Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED,
                  Settings.Secure.TV_APP_USES_NON_SYSTEM_INPUTS,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index e40fcb2..d6a8197 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -60,7 +60,9 @@
             // except for SystemUI-core.
             // Copied from compose/features/Android.bp.
             static_libs: [
+                "CommunalLayoutLib",
                 "PlatformComposeCore",
+                "PlatformComposeSceneTransitionLayout",
 
                 "androidx.compose.runtime_runtime",
                 "androidx.compose.material3_material3",
@@ -435,6 +437,7 @@
         "SystemUI-statsd",
         "SettingsLib",
         "com_android_systemui_flags_lib",
+        "flag-junit-base",
         "androidx.viewpager2_viewpager2",
         "androidx.legacy_legacy-support-v4",
         "androidx.recyclerview_recyclerview",
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/accessibility/accessibilitymenu/res/values-eu/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml
index f6dcdd3..b314c8e 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml
@@ -8,7 +8,7 @@
     <string name="a11y_settings_label" msgid="3977714687248445050">"Erabilerraztasun-&amp;#173;ezarpenak"</string>
     <string name="power_label" msgid="7699720321491287839">"Bateria"</string>
     <string name="power_utterance" msgid="7444296686402104807">"Bateria kontrolatzeko aukerak"</string>
-    <string name="recent_apps_label" msgid="6583276995616385847">"Azken aplikazioak"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Azkenaldiko aplikazioak"</string>
     <string name="lockscreen_label" msgid="648347953557887087">"Pantaila blokeatua"</string>
     <string name="quick_settings_label" msgid="2999117381487601865">"Ezarpen bizkorrak"</string>
     <string name="notifications_label" msgid="6829741046963013567">"Jakinarazpenak"</string>
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index c1390b2..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",
     ],
 }
 
diff --git a/packages/SystemUI/aconfig/communal.aconfig b/packages/SystemUI/aconfig/communal.aconfig
new file mode 100644
index 0000000..2c6ff97
--- /dev/null
+++ b/packages/SystemUI/aconfig/communal.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.systemui"
+
+flag {
+    name: "communal_hub"
+    namespace: "communal"
+    description: "Enables the communal hub experience"
+    bug: "304584416"
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 18117a8..2509cfd 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -21,3 +21,11 @@
         "(containing the \"Clear all\" button). Should not bring any behavior changes"
     bug: "293167744"
 }
+
+flag {
+    name: "notification_lifetime_extension_refactor"
+    namespace: "systemui"
+    description: "Enables moving notification lifetime extension management from SystemUI to "
+        "Notification Manager Service"
+    bug: "299448097"
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 4aac279..4ea57a8 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -893,7 +893,7 @@
                 return
             }
 
-            Log.i(TAG, "Remote animation timed out")
+            Log.wtf(TAG, "Remote animation timed out")
             timedOut = true
 
             if (DEBUG_LAUNCH_ANIMATION) {
diff --git a/packages/SystemUI/communal/layout/Android.bp b/packages/SystemUI/communal/layout/Android.bp
new file mode 100644
index 0000000..88dad66
--- /dev/null
+++ b/packages/SystemUI/communal/layout/Android.bp
@@ -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 {
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+    name: "CommunalLayoutLib",
+    srcs: [
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "androidx.arch.core_core-runtime",
+        "androidx.compose.animation_animation-graphics",
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.material3_material3",
+        "jsr330",
+        "kotlinx-coroutines-android",
+        "kotlinx-coroutines-core",
+    ],
+    manifest: "AndroidManifest.xml",
+    kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SystemUI/communal/layout/AndroidManifest.xml b/packages/SystemUI/communal/layout/AndroidManifest.xml
new file mode 100644
index 0000000..141be07
--- /dev/null
+++ b/packages/SystemUI/communal/layout/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?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 package="com.android.systemui.communal.layout" />
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt
new file mode 100644
index 0000000..df87d19d
--- /dev/null
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.communal.layout
+
+import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
+
+/** Computes the arrangement of cards. */
+class CommunalLayoutEngine {
+    companion object {
+        /**
+         * Determines the size that each card should be rendered in, and distributes the cards into
+         * columns.
+         *
+         * Returns a nested list where the outer list contains columns, and the inner list contains
+         * cards in each column.
+         *
+         * Currently treats the first supported size as the size to be rendered in, ignoring other
+         * supported sizes.
+         */
+        fun distributeCardsIntoColumns(
+            cards: List<CommunalGridLayoutCard>,
+        ): List<List<CommunalGridLayoutCardInfo>> {
+            val result = ArrayList<ArrayList<CommunalGridLayoutCardInfo>>()
+
+            var capacityOfLastColumn = 0
+            for (card in cards) {
+                val cardSize = card.supportedSizes.first()
+                if (capacityOfLastColumn >= cardSize.value) {
+                    // Card fits in last column
+                    capacityOfLastColumn -= cardSize.value
+                } else {
+                    // Create a new column
+                    result.add(arrayListOf())
+                    capacityOfLastColumn = CommunalGridLayoutCard.Size.FULL.value - cardSize.value
+                }
+
+                result.last().add(CommunalGridLayoutCardInfo(card, cardSize))
+            }
+
+            return result
+        }
+    }
+
+    /**
+     * A data class that wraps around a [CommunalGridLayoutCard] and also contains the size that the
+     * card should be rendered in.
+     */
+    data class CommunalGridLayoutCardInfo(
+        val card: CommunalGridLayoutCard,
+        val size: CommunalGridLayoutCard.Size,
+    )
+}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
new file mode 100644
index 0000000..4ed78b3
--- /dev/null
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.layout.ui.compose
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.systemui.communal.layout.CommunalLayoutEngine
+import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
+import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig
+
+/**
+ * An arrangement of cards with a horizontal scroll, where each card is displayed in the right size
+ * and follows a specific order based on its priority, ensuring a seamless layout without any gaps.
+ */
+@Composable
+fun CommunalGridLayout(
+    modifier: Modifier,
+    layoutConfig: CommunalGridLayoutConfig,
+    communalCards: List<CommunalGridLayoutCard>,
+) {
+    val columns = CommunalLayoutEngine.distributeCardsIntoColumns(communalCards)
+    LazyRow(
+        modifier = modifier.height(layoutConfig.gridHeight),
+        horizontalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter),
+    ) {
+        for (column in columns) {
+            item {
+                Column(
+                    modifier = Modifier.width(layoutConfig.cardWidth),
+                    verticalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter),
+                ) {
+                    for (cardInfo in column) {
+                        Row(
+                            modifier = Modifier.height(layoutConfig.cardHeight(cardInfo.size)),
+                        ) {
+                            cardInfo.card.Content(Modifier.fillMaxSize())
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
new file mode 100644
index 0000000..ac8aa67
--- /dev/null
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.layout.ui.compose.config
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+/** A card that hosts content to be rendered in the communal grid layout. */
+abstract class CommunalGridLayoutCard {
+    /**
+     * Content to be hosted by the card.
+     *
+     * To host non-Compose views, see
+     * https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose.
+     */
+    @Composable abstract fun Content(modifier: Modifier)
+
+    /**
+     * Sizes supported by the card.
+     *
+     * If multiple sizes are available, they should be ranked in order of preference, from most to
+     * least preferred.
+     */
+    abstract val supportedSizes: List<Size>
+
+    /**
+     * Priority of the content hosted by the card.
+     *
+     * The value of priority is relative to other cards. Cards with a higher priority are generally
+     * ordered first.
+     */
+    open val priority: Int = 0
+
+    /**
+     * Size of the card.
+     *
+     * @param value A numeric value that represents the size. Must be less than or equal to
+     *   [Size.FULL].
+     */
+    enum class Size(val value: Int) {
+        /** The card takes up full height of the grid layout. */
+        FULL(value = 6),
+
+        /** The card takes up half of the vertical space of the grid layout. */
+        HALF(value = 3),
+
+        /** The card takes up a third of the vertical space of the grid layout. */
+        THIRD(value = 2),
+    }
+}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt
new file mode 100644
index 0000000..143df83
--- /dev/null
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.communal.layout.ui.compose.config
+
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.times
+
+/**
+ * Configurations of the communal grid layout.
+ *
+ * The communal grid layout follows Material Design's responsive layout grid (see
+ * https://m2.material.io/design/layout/responsive-layout-grid.html), in which the layout is divided
+ * up by columns and gutters, and each card occupies one or multiple columns.
+ */
+data class CommunalGridLayoutConfig(
+    /**
+     * Size in dp of each grid column.
+     *
+     * Every card occupies one or more grid columns, which means that the width of each card is
+     * influenced by the size of the grid columns.
+     */
+    val gridColumnSize: Dp,
+
+    /**
+     * Size in dp of each grid gutter.
+     *
+     * A gutter is the space between columns that helps separate content. This is, therefore, also
+     * the size of the gaps between cards, both horizontally and vertically.
+     */
+    val gridGutter: Dp,
+
+    /**
+     * Height in dp of the grid layout.
+     *
+     * Cards with a full size take up the entire height of the grid layout.
+     */
+    val gridHeight: Dp,
+
+    /**
+     * Number of grid columns that each card occupies.
+     *
+     * It's important to note that all the cards take up the same number of grid columns, or in
+     * simpler terms, they all have the same width.
+     */
+    val gridColumnsPerCard: Int,
+) {
+    /**
+     * Width in dp of each card.
+     *
+     * It's important to note that all the cards take up the same number of grid columns, or in
+     * simpler terms, they all have the same width.
+     */
+    val cardWidth = gridColumnSize * gridColumnsPerCard + gridGutter * (gridColumnsPerCard - 1)
+
+    /** Returns the height of a card in dp, based on its size. */
+    fun cardHeight(cardSize: CommunalGridLayoutCard.Size): Dp {
+        return when (cardSize) {
+            CommunalGridLayoutCard.Size.FULL -> cardHeightBy(denominator = 1)
+            CommunalGridLayoutCard.Size.HALF -> cardHeightBy(denominator = 2)
+            CommunalGridLayoutCard.Size.THIRD -> cardHeightBy(denominator = 3)
+        }
+    }
+
+    /** Returns the height of a card in dp when the layout is evenly divided by [denominator]. */
+    private fun cardHeightBy(denominator: Int): Dp {
+        return (gridHeight - (denominator - 1) * gridGutter) / denominator
+    }
+}
diff --git a/packages/SystemUI/communal/layout/tests/Android.bp b/packages/SystemUI/communal/layout/tests/Android.bp
new file mode 100644
index 0000000..9a05504
--- /dev/null
+++ b/packages/SystemUI/communal/layout/tests/Android.bp
@@ -0,0 +1,47 @@
+// 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 {
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_test {
+    name: "CommunalLayoutLibTests",
+    srcs: [
+        "**/*.kt",
+    ],
+    static_libs: [
+        "CommunalLayoutLib",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "frameworks-base-testutils",
+        "junit",
+        "kotlinx_coroutines_test",
+        "mockito-target-extended-minus-junit4",
+        "platform-test-annotations",
+        "testables",
+        "truth",
+    ],
+    libs: [
+        "android.test.mock",
+        "android.test.base",
+        "android.test.runner",
+    ],
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+    manifest: "AndroidManifest.xml",
+}
diff --git a/packages/SystemUI/communal/layout/tests/AndroidManifest.xml b/packages/SystemUI/communal/layout/tests/AndroidManifest.xml
new file mode 100644
index 0000000..b19007c
--- /dev/null
+++ b/packages/SystemUI/communal/layout/tests/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.systemui.communal.layout.tests">
+
+    <application android:debuggable="true" android:largeHeap="true">
+        <uses-library android:name="android.test.mock" />
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.testing.TestableInstrumentation"
+        android:targetPackage="com.android.systemui.communal.layout.tests"
+        android:label="Tests for CommunalLayoutLib">
+    </instrumentation>
+
+</manifest>
diff --git a/packages/SystemUI/communal/layout/tests/AndroidTest.xml b/packages/SystemUI/communal/layout/tests/AndroidTest.xml
new file mode 100644
index 0000000..1352b23
--- /dev/null
+++ b/packages/SystemUI/communal/layout/tests/AndroidTest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration description="Runs tests for CommunalLayoutLib">
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="CommunalLayoutLibTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="framework-base-presubmit" />
+    <option name="test-tag" value="CommunalLayoutLibTests" />
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.systemui.communal.layout.tests" />
+        <option name="runner" value="android.testing.TestableInstrumentation" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+
+</configuration>
diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
new file mode 100644
index 0000000..fdf65f5
--- /dev/null
+++ b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
@@ -0,0 +1,99 @@
+package com.android.systemui.communal.layout
+
+import androidx.compose.material3.Card
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalLayoutEngineTest {
+    @Test
+    fun distribution_fullLayout() {
+        val cards =
+            listOf(
+                generateCard(CommunalGridLayoutCard.Size.FULL),
+                generateCard(CommunalGridLayoutCard.Size.HALF),
+                generateCard(CommunalGridLayoutCard.Size.HALF),
+                generateCard(CommunalGridLayoutCard.Size.THIRD),
+                generateCard(CommunalGridLayoutCard.Size.THIRD),
+                generateCard(CommunalGridLayoutCard.Size.THIRD),
+            )
+        val expected =
+            listOf(
+                listOf(
+                    CommunalGridLayoutCard.Size.FULL,
+                ),
+                listOf(
+                    CommunalGridLayoutCard.Size.HALF,
+                    CommunalGridLayoutCard.Size.HALF,
+                ),
+                listOf(
+                    CommunalGridLayoutCard.Size.THIRD,
+                    CommunalGridLayoutCard.Size.THIRD,
+                    CommunalGridLayoutCard.Size.THIRD,
+                ),
+            )
+
+        assertDistribution(cards, expected)
+    }
+
+    @Test
+    fun distribution_layoutWithGaps() {
+        val cards =
+            listOf(
+                generateCard(CommunalGridLayoutCard.Size.HALF),
+                generateCard(CommunalGridLayoutCard.Size.THIRD),
+                generateCard(CommunalGridLayoutCard.Size.HALF),
+                generateCard(CommunalGridLayoutCard.Size.FULL),
+                generateCard(CommunalGridLayoutCard.Size.THIRD),
+            )
+        val expected =
+            listOf(
+                listOf(
+                    CommunalGridLayoutCard.Size.HALF,
+                    CommunalGridLayoutCard.Size.THIRD,
+                ),
+                listOf(
+                    CommunalGridLayoutCard.Size.HALF,
+                ),
+                listOf(
+                    CommunalGridLayoutCard.Size.FULL,
+                ),
+                listOf(
+                    CommunalGridLayoutCard.Size.THIRD,
+                ),
+            )
+
+        assertDistribution(cards, expected)
+    }
+
+    private fun assertDistribution(
+        cards: List<CommunalGridLayoutCard>,
+        expected: List<List<CommunalGridLayoutCard.Size>>,
+    ) {
+        val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards)
+
+        for (c in expected.indices) {
+            for (r in expected[c].indices) {
+                assertThat(result[c][r].size).isEqualTo(expected[c][r])
+            }
+        }
+    }
+
+    private fun generateCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard {
+        return object : CommunalGridLayoutCard() {
+            override val supportedSizes = listOf(size)
+
+            @Composable
+            override fun Content(modifier: Modifier) {
+                Card(modifier = modifier, content = {})
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt
new file mode 100644
index 0000000..946eeec
--- /dev/null
+++ b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt
@@ -0,0 +1,63 @@
+package com.android.systemui.communal.layout.ui.compose.config
+
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalGridLayoutConfigTest {
+    @Test
+    fun cardWidth() {
+        Truth.assertThat(
+                CommunalGridLayoutConfig(
+                        gridColumnSize = 5.dp,
+                        gridGutter = 3.dp,
+                        gridHeight = 17.dp,
+                        gridColumnsPerCard = 1,
+                    )
+                    .cardWidth
+            )
+            .isEqualTo(5.dp)
+
+        Truth.assertThat(
+                CommunalGridLayoutConfig(
+                        gridColumnSize = 5.dp,
+                        gridGutter = 3.dp,
+                        gridHeight = 17.dp,
+                        gridColumnsPerCard = 2,
+                    )
+                    .cardWidth
+            )
+            .isEqualTo(13.dp)
+
+        Truth.assertThat(
+                CommunalGridLayoutConfig(
+                        gridColumnSize = 5.dp,
+                        gridGutter = 3.dp,
+                        gridHeight = 17.dp,
+                        gridColumnsPerCard = 3,
+                    )
+                    .cardWidth
+            )
+            .isEqualTo(21.dp)
+    }
+
+    @Test
+    fun cardHeight() {
+        val config =
+            CommunalGridLayoutConfig(
+                gridColumnSize = 5.dp,
+                gridGutter = 2.dp,
+                gridHeight = 10.dp,
+                gridColumnsPerCard = 3,
+            )
+
+        Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.FULL)).isEqualTo(10.dp)
+        Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.HALF)).isEqualTo(4.dp)
+        Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.THIRD)).isEqualTo(2.dp)
+    }
+}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index 5b4a8fb..3d670b8 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -64,6 +64,12 @@
         throwComposeUnavailableError()
     }
 
+    override fun createCommunalView(
+        context: Context,
+    ): View {
+        throwComposeUnavailableError()
+    }
+
     private fun throwComposeUnavailableError(): Nothing {
         error(
             "Compose is not available. Make sure to check isComposeAvailable() before calling any" +
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index ac59989..7b11ac7 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
 import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
 import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
+import com.android.systemui.communal.ui.compose.CommunalHub
 import com.android.systemui.people.ui.compose.PeopleScreen
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -93,6 +94,12 @@
         }
     }
 
+    override fun createCommunalView(
+        context: Context,
+    ): View {
+        return ComposeView(context).apply { setContent { PlatformTheme { CommunalHub() } } }
+    }
+
     // TODO(b/298525212): remove once Compose exposes window inset bounds.
     private fun displayCutoutFromWindowInsets(
         scope: CoroutineScope,
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/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
new file mode 100644
index 0000000..4d2978d
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -0,0 +1,22 @@
+package com.android.systemui.communal.ui.compose
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+
+@Composable
+fun CommunalHub(modifier: Modifier = Modifier) {
+    Box(
+        modifier = modifier.fillMaxSize().background(Color.White),
+    ) {
+        Text(
+            modifier = Modifier.align(Alignment.Center),
+            text = "Hello Communal!",
+        )
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
index 0d2ba28..d1c12ac 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
@@ -16,14 +16,8 @@
 
 package com.android.systemui.communal.ui.compose
 
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.shared.model.Direction
@@ -51,13 +45,6 @@
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
-        Box(
-            modifier = modifier.fillMaxSize().background(Color.White),
-        ) {
-            Text(
-                modifier = Modifier.align(Alignment.Center),
-                text = "Hello Communal!",
-            )
-        }
+        CommunalHub(modifier)
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index c3a3752..ee310ab 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -36,13 +36,14 @@
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.core.view.isVisible
 import com.android.compose.animation.scene.SceneScope
-import com.android.systemui.res.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.qualifiers.KeyguardRootView
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.Edge
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.scene.shared.model.UserAction
@@ -99,7 +100,9 @@
         return buildMap {
             up?.let { this[UserAction.Swipe(Direction.UP)] = SceneModel(up) }
             left?.let { this[UserAction.Swipe(Direction.LEFT)] = SceneModel(left) }
-            this[UserAction.Swipe(Direction.DOWN)] = SceneModel(SceneKey.Shade)
+            this[UserAction.Swipe(fromEdge = Edge.TOP, direction = Direction.DOWN)] =
+                SceneModel(SceneKey.QuickSettings)
+            this[UserAction.Swipe(direction = Direction.DOWN)] = SceneModel(SceneKey.Shade)
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
index e12b7ea..73cb72c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
@@ -47,10 +47,10 @@
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
 import com.android.compose.theme.LocalAndroidColorScheme
-import com.android.systemui.res.R
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import com.android.systemui.res.R
 
 /**
  * Compose the screen associated to a [PeopleViewModel].
@@ -86,9 +86,9 @@
         modifier = Modifier.fillMaxSize(),
     ) {
         if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) {
-            PeopleScreenWithConversations(priorityTiles, recentTiles, viewModel::onTileClicked)
+            PeopleScreenWithConversations(priorityTiles, recentTiles, viewModel.onTileClicked)
         } else {
-            PeopleScreenEmpty(viewModel::onUserJourneyCancelled)
+            PeopleScreenEmpty(viewModel.onUserJourneyCancelled)
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 2ee461f..f35ea83 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -22,6 +22,7 @@
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.Edge
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.scene.shared.model.UserAction
@@ -41,7 +42,12 @@
     override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
         MutableStateFlow<Map<UserAction, SceneModel>>(
                 mapOf(
-                    UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade),
+                    UserAction.Swipe(
+                        pointerCount = 2,
+                        fromEdge = Edge.TOP,
+                        direction = Direction.DOWN,
+                    ) to SceneModel(SceneKey.QuickSettings),
+                    UserAction.Swipe(direction = Direction.DOWN) to SceneModel(SceneKey.Shade),
                 )
             )
             .asStateFlow()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 6359ce60..2e93a09 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -35,15 +35,18 @@
 import androidx.compose.ui.input.pointer.motionEventSpy
 import androidx.compose.ui.input.pointer.pointerInput
 import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Edge as SceneTransitionEdge
 import com.android.compose.animation.scene.ObservableTransitionState as SceneTransitionObservableTransitionState
 import com.android.compose.animation.scene.SceneKey as SceneTransitionSceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.animation.scene.SceneTransitionLayoutState
 import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserAction as SceneTransitionUserAction
 import com.android.compose.animation.scene.observableTransitionState
 import com.android.systemui.ribbon.ui.composable.BottomRightCornerRibbon
 import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.Edge
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -158,8 +161,7 @@
                 fromScene = fromScene.toModel().key,
                 toScene = toScene.toModel().key,
                 progress = progress,
-                isInitiatedByUserInput = isInitiatedByUserInput,
-                isUserInputOngoing = isUserInputOngoing,
+                isUserInputDriven = isUserInputDriven,
             )
     }
 }
@@ -181,12 +183,24 @@
 private fun UserAction.toTransitionUserAction(): SceneTransitionUserAction {
     return when (this) {
         is UserAction.Swipe ->
-            when (this.direction) {
-                Direction.LEFT -> Swipe.Left
-                Direction.UP -> Swipe.Up
-                Direction.RIGHT -> Swipe.Right
-                Direction.DOWN -> Swipe.Down
-            }
+            Swipe(
+                pointerCount = pointerCount,
+                fromEdge =
+                    when (this.fromEdge) {
+                        null -> null
+                        Edge.LEFT -> SceneTransitionEdge.Left
+                        Edge.TOP -> SceneTransitionEdge.Top
+                        Edge.RIGHT -> SceneTransitionEdge.Right
+                        Edge.BOTTOM -> SceneTransitionEdge.Bottom
+                    },
+                direction =
+                    when (this.direction) {
+                        Direction.LEFT -> SwipeDirection.Left
+                        Direction.UP -> SwipeDirection.Up
+                        Direction.RIGHT -> SwipeDirection.Right
+                        Direction.DOWN -> SwipeDirection.Down
+                    }
+            )
         is UserAction.Back -> Back
     }
 }
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 90%
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
index 60c3fd3..88944f10 100644
--- 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
@@ -108,7 +108,7 @@
 ) {
     val fromScene = layoutImpl.state.transitionState.currentScene
     val isUserInput =
-        (layoutImpl.state.transitionState as? TransitionState.Transition)?.isInitiatedByUserInput
+        (layoutImpl.state.transitionState as? TransitionState.Transition)?.isUserInputDriven
             ?: false
 
     val animationSpec = layoutImpl.transitions.transitionSpec(fromScene, target).spec
@@ -119,23 +119,9 @@
     val targetProgress = if (reversed) 0f else 1f
     val transition =
         if (reversed) {
-            OneOffTransition(
-                fromScene = target,
-                toScene = fromScene,
-                currentScene = target,
-                isUserInput,
-                isUserInputOngoing = false,
-                animatable,
-            )
+            OneOffTransition(target, fromScene, currentScene = target, isUserInput, animatable)
         } else {
-            OneOffTransition(
-                fromScene = fromScene,
-                toScene = target,
-                currentScene = target,
-                isUserInput,
-                isUserInputOngoing = false,
-                animatable,
-            )
+            OneOffTransition(fromScene, target, currentScene = target, isUserInput, animatable)
         }
 
     // Change the current layout state to use this new transition.
@@ -156,8 +142,7 @@
     override val fromScene: SceneKey,
     override val toScene: SceneKey,
     override val currentScene: SceneKey,
-    override val isInitiatedByUserInput: Boolean,
-    override val isUserInputOngoing: Boolean,
+    override val isUserInputDriven: Boolean,
     private val animatable: Animatable<Float, AnimationVector1D>,
 ) : TransitionState.Transition {
     override val progress: Float
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 97%
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
index 3bcd920f..ce96bbf 100644
--- 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
@@ -21,6 +21,7 @@
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.movableContentOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
@@ -59,6 +60,17 @@
     /** The mapping between a scene and the values/state this element has in that scene, if any. */
     val sceneValues = SnapshotStateMap<SceneKey, TargetValues>()
 
+    /**
+     * The movable content of this element, if this element is composed using
+     * [SceneScope.MovableElement].
+     */
+    val movableContent by
+        // This is only accessed from the composition (main) thread, so no need to use the default
+        // lock of lazy {} to synchronize.
+        lazy(mode = LazyThreadSafetyMode.NONE) {
+            movableContentOf { content: @Composable () -> Unit -> content() }
+        }
+
     override fun toString(): String {
         return "Element(key=$key)"
     }
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 91%
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
index b7acc48..bc015ee 100644
--- 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
@@ -22,7 +22,7 @@
  * A base class to create unique keys, associated to an [identity] that is used to check the
  * equality of two key instances.
  */
-sealed class Key(val name: String, val identity: Any) {
+sealed class Key(val debugName: String, val identity: Any) {
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (this.javaClass != other?.javaClass) return false
@@ -34,7 +34,7 @@
     }
 
     override fun toString(): String {
-        return "Key(name=$name)"
+        return "Key(debugName=$debugName)"
     }
 }
 
@@ -49,7 +49,7 @@
     val rootElementKey = ElementKey(name, identity)
 
     override fun toString(): String {
-        return "SceneKey(name=$name)"
+        return "SceneKey(debugName=$debugName)"
     }
 }
 
@@ -71,7 +71,7 @@
     }
 
     override fun toString(): String {
-        return "ElementKey(name=$name)"
+        return "ElementKey(debugName=$debugName)"
     }
 
     companion object {
@@ -89,6 +89,6 @@
 /** Key for a shared value of an element. */
 class ValueKey(name: String, identity: Any = Object()) : Key(name, identity) {
     override fun toString(): String {
-        return "ValueKey(name=$name)"
+        return "ValueKey(debugName=$debugName)"
     }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
new file mode 100644
index 0000000..6dbeb69
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -0,0 +1,200 @@
+/*
+ * 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 android.graphics.Picture
+import android.util.Log
+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
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.draw.drawWithCache
+import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.drawscope.draw
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntSize
+
+private const val TAG = "MovableElement"
+
+@Composable
+internal fun MovableElement(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: Scene,
+    key: ElementKey,
+    modifier: Modifier,
+    content: @Composable MovableElementScope.() -> Unit,
+) {
+    Box(modifier.element(layoutImpl, scene, key)) {
+        // Get the Element from the map. It will always be the same and we don't want to recompose
+        // 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
+        // which case we still need to draw it.
+        val picture = remember { Picture() }
+
+        if (shouldComposeMovableElement(layoutImpl, scene.key, element)) {
+            Box(
+                Modifier.drawWithCache {
+                    val width = size.width.toInt()
+                    val height = size.height.toInt()
+
+                    onDrawWithContent {
+                        // Save the draw commands into [picture] for later to draw the last content
+                        // even when this movable content is not composed.
+                        val pictureCanvas = Canvas(picture.beginRecording(width, height))
+                        draw(this, this.layoutDirection, pictureCanvas, this.size) {
+                            this@onDrawWithContent.drawContent()
+                        }
+                        picture.endRecording()
+
+                        // Draw the content.
+                        drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) }
+                    }
+                }
+            ) {
+                element.movableContent { movableElementScope.content() }
+            }
+        } else {
+            // If we are not composed, we draw the previous drawing commands at the same size as the
+            // movable content when it was composed in this scene.
+            val sceneValues = element.sceneValues.getValue(scene.key)
+
+            Spacer(
+                Modifier.layout { measurable, _ ->
+                        val size =
+                            sceneValues.targetSize.takeIf { it != Element.SizeUnspecified }
+                                ?: IntSize.Zero
+                        val placeable =
+                            measurable.measure(Constraints.fixed(size.width, size.height))
+                        layout(size.width, size.height) { placeable.place(0, 0) }
+                    }
+                    .drawBehind {
+                        drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) }
+                    }
+            )
+        }
+    }
+}
+
+private fun shouldComposeMovableElement(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: SceneKey,
+    element: Element,
+): Boolean {
+    val transitionState = layoutImpl.state.transitionState
+
+    // If we are idle, there is only one [scene] that is composed so we can compose our movable
+    // content here.
+    if (transitionState is TransitionState.Idle) {
+        check(transitionState.currentScene == scene)
+        return true
+    }
+
+    val fromScene = (transitionState as TransitionState.Transition).fromScene
+    val toScene = transitionState.toScene
+    if (fromScene == toScene) {
+        check(fromScene == scene)
+        return true
+    }
+
+    val fromReady = layoutImpl.isSceneReady(fromScene)
+    val toReady = layoutImpl.isSceneReady(toScene)
+
+    val otherScene =
+        when (scene) {
+            fromScene -> toScene
+            toScene -> fromScene
+            else ->
+                error(
+                    "shouldComposeMovableElement(scene=$scene) called with fromScene=$fromScene " +
+                        "and toScene=$toScene"
+                )
+        }
+
+    val isShared = otherScene in element.sceneValues
+
+    if (isShared && !toReady && !fromReady) {
+        // This should usually not happen given that fromScene should be ready, but let's log a
+        // warning here in case it does so it helps debugging flicker issues caused by this part of
+        // the code.
+        Log.w(
+            TAG,
+            "MovableElement $element might have to be composed for the first time in both " +
+                "fromScene=$fromScene and toScene=$toScene. This will probably lead to a flicker " +
+                "where the size of the element will jump from IntSize.Zero to its actual size " +
+                "during the transition."
+        )
+    }
+
+    // Element is not shared in this transition.
+    if (!isShared) {
+        return true
+    }
+
+    // toScene is not ready (because we are composing it for the first time), so we compose it there
+    // first. This is the most common scenario when starting a transition that has a shared movable
+    // element.
+    if (!toReady) {
+        return scene == toScene
+    }
+
+    // This should usually not happen, but if we are also composing for the first time in fromScene
+    // then we should compose it there only.
+    if (!fromReady) {
+        return scene == fromScene
+    }
+
+    // If we are ready in both scenes, then compose in the scene that has the highest zIndex (unless
+    // it is a background) given that this is the one that is going to be drawn.
+    val isHighestScene = layoutImpl.scene(scene).zIndex > layoutImpl.scene(otherScene).zIndex
+    return if (element.key.isBackground) {
+        !isHighestScene
+    } else {
+        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 87%
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
index 1b79dbd..ccdec6e 100644
--- 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
@@ -52,14 +52,7 @@
          * scene, this value will remain true after the pointer is no longer touching the screen and
          * will be true in any transition created to animate back to the original position.
          */
-        val isInitiatedByUserInput: Boolean,
-
-        /**
-         * Whether user input is currently driving the transition. For example, if a user is
-         * dragging a pointer, this emits true. Once they lift their finger, this emits false while
-         * the transition completes/settles.
-         */
-        val isUserInputOngoing: Flow<Boolean>,
+        val isUserInputDriven: Boolean,
     ) : ObservableTransitionState()
 }
 
@@ -80,8 +73,7 @@
                             fromScene = state.fromScene,
                             toScene = state.toScene,
                             progress = snapshotFlow { state.progress },
-                            isInitiatedByUserInput = state.isInitiatedByUserInput,
-                            isUserInputOngoing = snapshotFlow { state.isUserInputOngoing },
+                            isUserInputDriven = state.isUserInputDriven,
                         )
                     }
                 }
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 91%
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
index 3985233..3fd6828 100644
--- 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
@@ -90,4 +90,13 @@
             canOverflow,
         )
     }
+
+    @Composable
+    override fun MovableElement(
+        key: ElementKey,
+        modifier: Modifier,
+        content: @Composable MovableElementScope.() -> Unit,
+    ) {
+        MovableElement(layoutImpl, scene, key, modifier, content)
+    }
 }
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 71%
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 39173d9..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
@@ -85,6 +85,13 @@
     )
 }
 
+/**
+ * A DSL marker to prevent people from nesting calls to Modifier.element() inside a MovableElement,
+ * which is not supported.
+ */
+@DslMarker annotation class ElementDsl
+
+@ElementDsl
 interface SceneScope {
     /**
      * Tag an element identified by [key].
@@ -95,12 +102,37 @@
      * Additionally, this [key] will be used to detect elements that are shared between scenes to
      * automatically interpolate their size, offset and [shared values][animateSharedValueAsState].
      *
+     * Note that shared elements tagged using this function will be duplicated in each scene they
+     * are part of, so any **internal** state (e.g. state created using `remember {
+     * mutableStateOf(...) }`) will be lost. If you need to preserve internal state, you should use
+     * [MovableElement] instead.
+     *
+     * @see MovableElement
+     *
      * TODO(b/291566282): Migrate this to the new Modifier Node API and remove the @Composable
      *   constraint.
      */
     @Composable fun Modifier.element(key: ElementKey): Modifier
 
     /**
+     * Create a *movable* element identified by [key].
+     *
+     * This creates an element that will be automatically shared when present in multiple scenes and
+     * that can be transformed during transitions, the same way that [element] does. The major
+     * difference with [element] is that elements created with [MovableElement] will be "moved" and
+     * composed only once during transitions (as opposed to [element] that duplicates shared
+     * elements) so that any internal state is preserved during and after the transition.
+     *
+     * @see element
+     */
+    @Composable
+    fun MovableElement(
+        key: ElementKey,
+        modifier: Modifier,
+        content: @Composable MovableElementScope.() -> Unit,
+    )
+
+    /**
      * Animate some value of a shared element.
      *
      * @param value the value of this shared value in the current scene.
@@ -126,14 +158,40 @@
     ): State<T>
 }
 
+// TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey
+// arguments to allow sharing values inside a movable element.
+@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
 
 /** The user navigated back, either using a gesture or by triggering a KEYCODE_BACK event. */
-object Back : UserAction
+data object Back : UserAction
 
 /** The user swiped on the container. */
-enum class Swipe : UserAction {
+data class Swipe(
+    val direction: SwipeDirection,
+    val pointerCount: Int = 1,
+    val fromEdge: Edge? = null,
+) : UserAction {
+    companion object {
+        val Left = Swipe(SwipeDirection.Left)
+        val Up = Swipe(SwipeDirection.Up)
+        val Right = Swipe(SwipeDirection.Right)
+        val Down = Swipe(SwipeDirection.Down)
+    }
+}
+
+enum class SwipeDirection {
     Up,
     Down,
     Left,
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 98%
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
index b3a7a8e9..4952270 100644
--- 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
@@ -199,4 +199,6 @@
         return readyScenes.containsKey(transition.fromScene) &&
             readyScenes.containsKey(transition.toScene)
     }
+
+    internal fun isSceneReady(scene: SceneKey): Boolean = readyScenes.containsKey(scene)
 }
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 94%
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
index b9f83c5..7a21211 100644
--- 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
@@ -70,9 +70,6 @@
         val progress: Float
 
         /** Whether the transition was triggered by user input rather than being programmatic. */
-        val isInitiatedByUserInput: Boolean
-
-        /** Whether user input is currently driving the transition. */
-        val isUserInputOngoing: Boolean
+        val isUserInputDriven: Boolean
     }
 }
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 93%
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
index e275fca..1cbfe30 100644
--- 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
@@ -66,7 +66,7 @@
     // swipe in the other direction.
     val startDragImmediately =
         state == transition &&
-            !transition.isUserInputOngoing &&
+            transition.isAnimatingOffset &&
             !currentScene.shouldEnableSwipes(orientation.opposite())
 
     // The velocity threshold at which the intent of the user is to swipe up or down. It is the same
@@ -126,7 +126,7 @@
 
     override val progress: Float
         get() {
-            val offset = if (isUserInputOngoing) dragOffset else offsetAnimatable.value
+            val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
             if (distance == 0f) {
                 // This can happen only if fromScene == toScene.
                 error(
@@ -137,15 +137,16 @@
             return offset / distance
         }
 
-    override val isInitiatedByUserInput = true
-
-    var _isUserInputOngoing by mutableStateOf(false)
-    override val isUserInputOngoing: Boolean
-        get() = _isUserInputOngoing
+    override val isUserInputDriven = true
 
     /** The current offset caused by the drag gesture. */
     var dragOffset by mutableFloatStateOf(0f)
 
+    /**
+     * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture.
+     */
+    var isAnimatingOffset by mutableStateOf(false)
+
     /** The animatable used to animate the offset once the user lifted its finger. */
     val offsetAnimatable = Animatable(0f, visibilityThreshold = OffsetVisibilityThreshold)
 
@@ -208,11 +209,9 @@
     transition: SwipeTransition,
     orientation: Orientation,
 ) {
-    transition._isUserInputOngoing = true
-
     if (layoutImpl.state.transitionState == transition) {
         // This [transition] was already driving the animation: simply take over it.
-        if (!transition.isUserInputOngoing) {
+        if (transition.isAnimatingOffset) {
             // Stop animating and start from where the current offset. Setting the animation job to
             // `null` will effectively cancel the animation.
             transition.stopOffsetAnimation()
@@ -457,29 +456,30 @@
 ) {
     transition.startOffsetAnimation {
         launch {
-            if (transition.isUserInputOngoing) {
-                transition.offsetAnimatable.snapTo(transition.dragOffset)
-            }
-            transition._isUserInputOngoing = false
+                if (!transition.isAnimatingOffset) {
+                    transition.offsetAnimatable.snapTo(transition.dragOffset)
+                }
+                transition.isAnimatingOffset = true
 
-            transition.offsetAnimatable.animateTo(
-                targetOffset,
-                // TODO(b/290184746): Make this spring spec configurable.
-                spring(
-                    stiffness = Spring.StiffnessMediumLow,
-                    visibilityThreshold = OffsetVisibilityThreshold
-                ),
-                initialVelocity = initialVelocity,
-            )
+                transition.offsetAnimatable.animateTo(
+                    targetOffset,
+                    // TODO(b/290184746): Make this spring spec configurable.
+                    spring(
+                        stiffness = Spring.StiffnessMediumLow,
+                        visibilityThreshold = OffsetVisibilityThreshold
+                    ),
+                    initialVelocity = initialVelocity,
+                )
 
-            // Now that the animation is done, the state should be idle. Note that if the state
-            // was changed since this animation started, some external code changed it and we
-            // shouldn't do anything here. Note also that this job will be cancelled in the case
-            // where the user intercepts this swipe.
-            if (layoutImpl.state.transitionState == transition) {
-                layoutImpl.state.transitionState = TransitionState.Idle(targetScene)
+                // Now that the animation is done, the state should be idle. Note that if the state
+                // was changed since this animation started, some external code changed it and we
+                // shouldn't do anything here. Note also that this job will be cancelled in the case
+                // where the user intercepts this swipe.
+                if (layoutImpl.state.transitionState == transition) {
+                    layoutImpl.state.transitionState = TransitionState.Idle(targetScene)
+                }
             }
-        }
+            .also { it.invokeOnCompletion { transition.isAnimatingOffset = false } }
     }
 }
 
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/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
new file mode 100644
index 0000000..4204cd5
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -0,0 +1,207 @@
+/*
+ * 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.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.hasParent
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.test.assertSizeIsEqualTo
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MovableElementTest {
+    @get:Rule val rule = createComposeRule()
+
+    /** An element that displays a counter that is incremented whenever this element is clicked. */
+    @Composable
+    private fun Counter(modifier: Modifier = Modifier) {
+        var count by remember { mutableIntStateOf(0) }
+        Box(modifier.fillMaxSize().clickable { count++ }) { Text("count: $count") }
+    }
+
+    @Composable
+    private fun SceneScope.MovableCounter(key: ElementKey, modifier: Modifier) {
+        MovableElement(key, modifier) { Counter() }
+    }
+
+    @Test
+    fun modifierElementIsDuplicatedDuringTransitions() {
+        rule.testTransition(
+            fromSceneContent = {
+                Box(Modifier.element(TestElements.Foo).size(50.dp)) { Counter() }
+            },
+            toSceneContent = { Box(Modifier.element(TestElements.Foo).size(100.dp)) { Counter() } },
+            transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) },
+            fromScene = TestScenes.SceneA,
+            toScene = TestScenes.SceneB,
+        ) {
+            before {
+                // Click 3 times on the counter.
+                rule.onNodeWithText("count: 0").assertIsDisplayed().performClick()
+                rule.onNodeWithText("count: 1").assertIsDisplayed().performClick()
+                rule.onNodeWithText("count: 2").assertIsDisplayed().performClick()
+                rule
+                    .onNodeWithText("count: 3")
+                    .assertIsDisplayed()
+                    .assertSizeIsEqualTo(50.dp, 50.dp)
+
+                // There are no other counters.
+                assertThat(
+                        rule
+                            .onAllNodesWithText("count: ", substring = true)
+                            .fetchSemanticsNodes()
+                            .size
+                    )
+                    .isEqualTo(1)
+            }
+
+            at(32) {
+                // In the middle of the transition, there are 2 copies of the counter: the previous
+                // one from scene A (equal to 3) and the new one from scene B (equal to 0).
+                rule
+                    .onNode(
+                        hasText("count: 3") and
+                            hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA))
+                    )
+                    .assertIsDisplayed()
+                    .assertSizeIsEqualTo(75.dp, 75.dp)
+
+                rule
+                    .onNode(
+                        hasText("count: 0") and
+                            hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneB))
+                    )
+                    .assertIsDisplayed()
+                    .assertSizeIsEqualTo(75.dp, 75.dp)
+
+                // There are exactly 2 counters.
+                assertThat(
+                        rule
+                            .onAllNodesWithText("count: ", substring = true)
+                            .fetchSemanticsNodes()
+                            .size
+                    )
+                    .isEqualTo(2)
+            }
+
+            after {
+                // At the end of the transition, only the counter from scene B is composed.
+                rule
+                    .onNodeWithText("count: 0")
+                    .assertIsDisplayed()
+                    .assertSizeIsEqualTo(100.dp, 100.dp)
+
+                // There are no other counters.
+                assertThat(
+                        rule
+                            .onAllNodesWithText("count: ", substring = true)
+                            .fetchSemanticsNodes()
+                            .size
+                    )
+                    .isEqualTo(1)
+            }
+        }
+    }
+
+    @Test
+    fun movableElementIsMovedAndComposedOnlyOnce() {
+        rule.testTransition(
+            fromSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(50.dp)) },
+            toSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(100.dp)) },
+            transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) },
+            fromScene = TestScenes.SceneA,
+            toScene = TestScenes.SceneB,
+        ) {
+            before {
+                // Click 3 times on the counter.
+                rule.onNodeWithText("count: 0").assertIsDisplayed().performClick()
+                rule.onNodeWithText("count: 1").assertIsDisplayed().performClick()
+                rule.onNodeWithText("count: 2").assertIsDisplayed().performClick()
+                rule
+                    .onNodeWithText("count: 3")
+                    .assertIsDisplayed()
+                    .assertSizeIsEqualTo(50.dp, 50.dp)
+
+                // There are no other counters.
+                assertThat(
+                        rule
+                            .onAllNodesWithText("count: ", substring = true)
+                            .fetchSemanticsNodes()
+                            .size
+                    )
+                    .isEqualTo(1)
+            }
+
+            at(32) {
+                // During the transition, there is a single counter that is moved, with the current
+                // value.
+                rule
+                    .onNode(hasText("count: 3"))
+                    .assertIsDisplayed()
+                    .assertSizeIsEqualTo(75.dp, 75.dp)
+
+                // There are no other counters.
+                assertThat(
+                        rule
+                            .onAllNodesWithText("count: ", substring = true)
+                            .fetchSemanticsNodes()
+                            .size
+                    )
+                    .isEqualTo(1)
+            }
+
+            after {
+                // At the end of the transition, the counter still has the current value.
+                rule
+                    .onNodeWithText("count: 3")
+                    .assertIsDisplayed()
+                    .assertSizeIsEqualTo(100.dp, 100.dp)
+
+                // There are no other counters.
+                assertThat(
+                        rule
+                            .onAllNodesWithText("count: ", substring = true)
+                            .fetchSemanticsNodes()
+                            .size
+                    )
+                    .isEqualTo(1)
+            }
+        }
+    }
+}
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 98%
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
index 328866e..5afd420 100644
--- 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
@@ -117,7 +117,7 @@
                 .size(size)
                 .background(Color.Red)
                 .element(TestElements.Foo)
-                .testTag(TestElements.Foo.name)
+                .testTag(TestElements.Foo.debugName)
         ) {
             // Offset the single child of Foo by some animated shared offset.
             val offset by animateSharedDpAsState(childOffset, TestValues.Value1, TestElements.Foo)
@@ -129,7 +129,7 @@
                     }
                     .size(30.dp)
                     .background(Color.Blue)
-                    .testTag(TestElements.Bar.name)
+                    .testTag(TestElements.Bar.debugName)
             )
         }
     }
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 95%
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
index 53ed2b5..df3b72a 100644
--- 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
@@ -63,7 +63,7 @@
             { currentScene = it },
             EmptyTestTransitions,
             state = layoutState,
-            modifier = Modifier.size(LayoutWidth, LayoutHeight).testTag(TestElements.Foo.name),
+            modifier = Modifier.size(LayoutWidth, LayoutHeight).testTag(TestElements.Foo.debugName),
         ) {
             scene(
                 TestScenes.SceneA,
@@ -122,8 +122,7 @@
         assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
         assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA)
         assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth)
-        assertThat(transition.isInitiatedByUserInput).isTrue()
-        assertThat(transition.isUserInputOngoing).isTrue()
+        assertThat(transition.isUserInputDriven).isTrue()
 
         // Release the finger. We should now be animating back to A (currentScene = SceneA) given
         // that 55dp < positional threshold.
@@ -135,8 +134,7 @@
         assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
         assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA)
         assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth)
-        assertThat(transition.isInitiatedByUserInput).isTrue()
-        assertThat(transition.isUserInputOngoing).isFalse()
+        assertThat(transition.isUserInputDriven).isTrue()
 
         // Wait for the animation to finish. We should now be in scene A.
         rule.waitForIdle()
@@ -158,8 +156,7 @@
         assertThat(transition.toScene).isEqualTo(TestScenes.SceneC)
         assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA)
         assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight)
-        assertThat(transition.isInitiatedByUserInput).isTrue()
-        assertThat(transition.isUserInputOngoing).isTrue()
+        assertThat(transition.isUserInputDriven).isTrue()
 
         // Release the finger. We should now be animating to C (currentScene = SceneC) given
         // that 56dp >= positional threshold.
@@ -171,8 +168,7 @@
         assertThat(transition.toScene).isEqualTo(TestScenes.SceneC)
         assertThat(transition.currentScene).isEqualTo(TestScenes.SceneC)
         assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight)
-        assertThat(transition.isInitiatedByUserInput).isTrue()
-        assertThat(transition.isUserInputOngoing).isFalse()
+        assertThat(transition.isUserInputDriven).isTrue()
 
         // Wait for the animation to finish. We should now be in scene C.
         rule.waitForIdle()
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 95%
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
index 268057f..e0ae1be 100644
--- 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
@@ -22,13 +22,13 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.SemanticsMatcher
 import androidx.compose.ui.test.SemanticsNodeInteraction
 import androidx.compose.ui.test.SemanticsNodeInteractionCollection
 import androidx.compose.ui.test.hasParent
 import androidx.compose.ui.test.hasTestTag
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.onAllNodesWithTag
-import androidx.compose.ui.test.onNodeWithTag
 
 @DslMarker annotation class TransitionTestDsl
 
@@ -63,6 +63,8 @@
 
 @TransitionTestDsl
 interface TransitionTestAssertionScope {
+    fun isElement(element: ElementKey, scene: SceneKey? = null): SemanticsMatcher
+
     /**
      * Assert on [element].
      *
@@ -130,15 +132,19 @@
     val test = transitionTest(builder)
     val assertionScope =
         object : TransitionTestAssertionScope {
+            override fun isElement(element: ElementKey, scene: SceneKey?): SemanticsMatcher {
+                return if (scene == null) {
+                    hasTestTag(element.testTag)
+                } else {
+                    hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag))
+                }
+            }
+
             override fun onElement(
                 element: ElementKey,
                 scene: SceneKey?
             ): SemanticsNodeInteraction {
-                return if (scene == null) {
-                    onNodeWithTag(element.testTag)
-                } else {
-                    onNode(hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag)))
-                }
+                return onNode(isElement(element, scene))
             }
 
             override fun onSharedElement(element: ElementKey): SemanticsNodeInteractionCollection {
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/drawable/progress_bar.xml b/packages/SystemUI/res-keyguard/drawable/progress_bar.xml
new file mode 100644
index 0000000..910a74a
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/progress_bar.xml
@@ -0,0 +1,45 @@
+<?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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:paddingMode="stack">
+    <item
+        android:id="@android:id/background"
+        android:gravity="center_vertical|fill_horizontal">
+        <shape
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:shape="rectangle">
+            <corners android:radius="30dp" />
+            <solid android:color="?androidprv:attr/materialColorSurfaceContainerHighest" />
+        </shape>
+    </item>
+    <item
+        android:id="@android:id/progress"
+        android:gravity="center_vertical|fill_horizontal">
+        <clip>
+            <shape
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:shape="rectangle">
+                <corners android:radius="30dp" />
+                <solid android:color="?androidprv:attr/textColorPrimary" />
+            </shape>
+        </clip>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml b/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml
new file mode 100644
index 0000000..183f0e5
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml
@@ -0,0 +1,32 @@
+<?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.
+  ~
+  -->
+
+<LinearLayout android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:orientation="vertical"
+    android:layoutDirection="ltr"
+    android:gravity="center"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <ProgressBar
+        android:id="@+id/side_fps_progress_bar"
+        android:layout_width="55dp"
+        android:layout_height="10dp"
+        android:indeterminateOnly="false"
+        android:min="0"
+        android:max="100"
+        android:progressDrawable="@drawable/progress_bar" />
+</LinearLayout>
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-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml
index 69f533c..67b4e4b 100644
--- a/packages/SystemUI/res-keyguard/values-bn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml
@@ -125,7 +125,7 @@
     <string name="clock_title_bubble" msgid="2204559396790593213">"বাবল"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"অ্যানালগ"</string>
     <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"চালিয়ে যেতে আপনার ডিভাইস আনলক করুন"</string>
-    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"পরে ইনস্টল আপডেট করতে পিন লিখুন"</string>
+    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"পরে আপডেট ইনস্টল করতে পিন লিখুন"</string>
     <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"পরে আপডেট ইনস্টল করতে পাসওয়ার্ড লিখুন"</string>
     <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"পরে আপডেট ইনস্টল করতে প্যাটার্ন আঁকুন"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"ডিভাইস আপডেট করা হয়েছে। চালিয়ে যেতে পিন লিখুন।"</string>
diff --git a/packages/SystemUI/res-keyguard/values-cs/strings.xml b/packages/SystemUI/res-keyguard/values-cs/strings.xml
index e075d85..573638b 100644
--- a/packages/SystemUI/res-keyguard/values-cs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-cs/strings.xml
@@ -125,9 +125,9 @@
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bublina"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogové"</string>
     <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Pokud chcete pokračovat, odemkněte zařízení"</string>
-    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Pokud aktualizaci chcete nainstalovat později, zadejte PIN"</string>
-    <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Pokud aktualizaci chcete nainstalovat později, zadejte heslo"</string>
-    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Pokud aktualizaci chcete nainstalovat později, zadejte gesto"</string>
+    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Zadejte PIN a aktualizaci nainstalujte později"</string>
+    <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Zadejte heslo a aktualizaci nainstalujte později"</string>
+    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Zadejte gesto a aktualizaci nainstalujte později"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Zařízení bylo aktualizováno. Pokud chcete pokračovat, zadejte PIN."</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Zařízení bylo aktualizováno. Pokud chcete pokračovat, zadejte heslo."</string>
     <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Zařízení bylo aktualizováno. Pokud chcete pokračovat, zadejte gesto."</string>
diff --git a/packages/SystemUI/res-keyguard/values-de/strings.xml b/packages/SystemUI/res-keyguard/values-de/strings.xml
index 117f7a9..5c5f264 100644
--- a/packages/SystemUI/res-keyguard/values-de/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-de/strings.xml
@@ -125,9 +125,9 @@
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
     <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Gerät entsperren, um fortzufahren"</string>
-    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Gib deine PIN ein, um das Update später zu installieren"</string>
-    <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Gib dein Passwort ein, um das Update später zu installieren"</string>
-    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Zeichne dein Muster, um das Update später zu installieren"</string>
+    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"PIN eingeben, um Update später zu installieren"</string>
+    <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Passwort eingeben, um Update später zu installieren"</string>
+    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Muster zeichnen, um Update später zu installieren"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Gerät aktualisiert. Gib deine PIN ein, um fortzufahren."</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Gerät aktualisiert. Gib dein Passwort ein, um fortzufahren."</string>
     <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Gerät aktualisiert. Zeichne dein Muster, um fortzufahren."</string>
diff --git a/packages/SystemUI/res-keyguard/values-el/strings.xml b/packages/SystemUI/res-keyguard/values-el/strings.xml
index cd7637c..3a01da5 100644
--- a/packages/SystemUI/res-keyguard/values-el/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-el/strings.xml
@@ -125,9 +125,9 @@
     <string name="clock_title_bubble" msgid="2204559396790593213">"Συννεφάκι"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Αναλογικό"</string>
     <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Ξεκλειδώστε τη συσκευή σας για να συνεχίσετε"</string>
-    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Εισαγάγετε το PIN για να εγκαταστήσετε την ενημέρωση αργότερα"</string>
+    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Εισαγωγή PIN για εγκατάσταση ενημέρωσης αργότερα"</string>
     <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Εισαγ. τον κωδ. πρόσβασης για να εγκαταστήσετε την ενημέρωση αργότερα"</string>
-    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Σχεδιάστε το μοτίβο για να εγκαταστήσετε την ενημέρωση αργότερα"</string>
+    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Σχεδιάστε το μοτίβο για εγκατάσταση της ενημέρωσης αργότερα"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Η συσκευή ενημερώθηκε. Εισαγάγετε το PIN για να συνεχίσετε."</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Η συσκευή ενημερώθηκε. Εισαγάγ. τον κωδ. πρόσβασης για να συνεχίσετε."</string>
     <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Η συσκευή ενημερώθηκε. Σχεδιάστε το μοτίβο για να συνεχίσετε."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fa/strings.xml b/packages/SystemUI/res-keyguard/values-fa/strings.xml
index 4815815..ae3f04a 100644
--- a/packages/SystemUI/res-keyguard/values-fa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fa/strings.xml
@@ -125,9 +125,9 @@
     <string name="clock_title_bubble" msgid="2204559396790593213">"حباب"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"آنالوگ"</string>
     <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"برای ادامه، قفل دستگاهتان را باز کنید"</string>
-    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"برای نصب به‌روزرسانی در فرصتی دیگر، پین را وارد کنید"</string>
-    <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"برای نصب به‌روزرسانی در فرصتی دیگر، گذرواژه را وارد کنید"</string>
-    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"برای نصب به‌روزرسانی در فرصتی دیگر، الگو را وارد کنید"</string>
+    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"پین را وارد کنید و به‌روزرسانی را در فرصتی دیگر انجام دهید"</string>
+    <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"گذرواژه را وارد کنید و به‌روزرسانی را در فرصتی دیگر انجام دهید"</string>
+    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"الگو را وارد کنید و به‌روزرسانی را در فرصتی دیگر انجام دهید"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"دستگاه به‌روز شد. برای ادامه، پین را وارد کنید."</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"دستگاه به‌روز شد. برای ادامه، گذرواژه را وارد کنید."</string>
     <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"دستگاه به‌روز شد. برای ادامه، الگو را وارد کنید."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fi/strings.xml b/packages/SystemUI/res-keyguard/values-fi/strings.xml
index 02d41d8..050df99 100644
--- a/packages/SystemUI/res-keyguard/values-fi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fi/strings.xml
@@ -127,7 +127,7 @@
     <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Jatka avaamalla laitteen lukitus"</string>
     <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Jos haluat asentaa päivityksen myöhemmin, lisää PIN-koodi"</string>
     <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Jos haluat asentaa päivityksen myöhemmin, lisää salasana"</string>
-    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Jos haluat asentaa päivityksen myöhemmin, piirrä kuvio"</string>
+    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Salli päivitys myöhemmin piirtämällä kuvio"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Laite päivitetty. Jatka lisäämällä PIN-koodi."</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Laite päivitetty. Jatka lisäämällä salasana."</string>
     <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Laite päivitetty. Jatka piirtämällä kuvio."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml
index e5be788..4309b56 100644
--- a/packages/SystemUI/res-keyguard/values-ro/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml
@@ -127,7 +127,7 @@
     <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Deblochează dispozitivul pentru a continua"</string>
     <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Introdu codul PIN pentru a instala actualizarea mai târziu"</string>
     <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Introdu parola pentru a instala actualizarea mai târziu"</string>
-    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Desenează modelul pentru a instala actualizarea mai târziu"</string>
+    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Desenează pentru a instala actualizarea mai târziu"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Dispozitivul s-a actualizat. Introdu codul PIN pentru a continua."</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Dispozitivul s-a actualizat. Introdu parola pentru a continua."</string>
     <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Dispozitivul s-a actualizat. Desenează modelul pentru a continua."</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
index 59261a3..4c65832 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
@@ -125,9 +125,9 @@
     <string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"指针"</string>
     <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"解锁设备才能继续操作"</string>
-    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"需要输入 PIN 码才能稍后安装更新"</string>
-    <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"需要输入密码才能稍后安装更新"</string>
-    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"需要绘制解锁图案才能稍后安装更新"</string>
+    <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"请输入 PIN 码,系统稍后会安装更新"</string>
+    <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"请输入密码,系统稍后会安装更新"</string>
+    <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"请绘制解锁图案,系统稍后会安装更新"</string>
     <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"设备已更新。您需要输入 PIN 码才能继续。"</string>
     <string name="kg_prompt_after_update_password" msgid="153703052501352094">"设备已更新。您需要输入密码才能继续。"</string>
     <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"设备已更新。您需要绘制解锁图案才能继续。"</string>
diff --git a/packages/SystemUI/res/color/qs_tile_ripple_color.xml b/packages/SystemUI/res/color/qs_tile_ripple_color.xml
new file mode 100644
index 0000000..c106254
--- /dev/null
+++ b/packages/SystemUI/res/color/qs_tile_ripple_color.xml
@@ -0,0 +1,25 @@
+<?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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="?android:attr/colorControlHighlight" android:state_hovered="true"
+        android:state_pressed="true" />
+    <!-- RippleDrawable has default way of handling hover state with highlighting but it's not
+    consistent with our approach so we make highlighting invisible and instead do custom handling
+    of hover state on a different level -->
+    <item android:color="@color/transparent" android:state_hovered="true" />
+    <item android:color="?android:attr/colorControlHighlight" />
+</selector>
\ No newline at end of file
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/drawable/qs_tile_background.xml b/packages/SystemUI/res/drawable/qs_tile_background.xml
index 265f575..ef3c61b 100644
--- a/packages/SystemUI/res/drawable/qs_tile_background.xml
+++ b/packages/SystemUI/res/drawable/qs_tile_background.xml
@@ -15,9 +15,25 @@
   ~ limitations under the License.
   -->
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="?android:attr/colorControlHighlight">
+    android:color="@color/qs_tile_ripple_color">
     <item android:id="@android:id/mask"
-        android:drawable="@drawable/qs_tile_background_shape" />
-    <item android:id="@id/background"
-        android:drawable="@drawable/qs_tile_background_shape"/>
+        android:drawable="@drawable/qs_tile_background_shape"
+        />
+    <item android:id="@id/background">
+        <layer-list>
+            <item
+                android:id="@+id/qs_tile_background_base"
+                android:drawable="@drawable/qs_tile_background_shape" />
+            <item android:id="@+id/qs_tile_background_overlay">
+                <selector>
+                    <item
+                        android:state_hovered="true"
+                        android:drawable="@drawable/qs_tile_background_shape" />
+                    <item
+                        android:state_focused="true"
+                        android:drawable="@drawable/qs_tile_background_shape" />
+                </selector>
+            </item>
+        </layer-list>
+    </item>
 </ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml
index 1c7e997..6d77943 100644
--- a/packages/SystemUI/res/layout/bluetooth_device_item.xml
+++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml
@@ -27,7 +27,6 @@
 
     <ImageView
         android:id="@+id/bluetooth_device_icon"
-        android:contentDescription="@string/accessibility_bluetooth_device_icon"
         android:layout_width="24dp"
         android:layout_height="24dp"
         app:layout_constraintStart_toStartOf="parent"
@@ -39,8 +38,12 @@
         android:layout_width="0dp"
         android:id="@+id/bluetooth_device_name"
         style="@style/BluetoothTileDialog.DeviceName"
+        android:textDirection="locale"
+        android:textAlignment="gravity"
         android:paddingStart="20dp"
         android:paddingTop="10dp"
+        android:maxLines="1"
+        android:ellipsize="end"
         app:layout_constraintWidth_percent="0.7"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon"
@@ -55,6 +58,8 @@
         style="@style/BluetoothTileDialog.DeviceSummary"
         android:paddingStart="20dp"
         android:paddingBottom="10dp"
+        android:maxLines="1"
+        android:ellipsize="end"
         app:layout_constraintWidth_percent="0.7"
         app:layout_constraintTop_toBottomOf="@+id/bluetooth_device_name"
         app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon"
@@ -66,6 +71,7 @@
         android:id="@+id/gear_icon"
         android:layout_width="0dp"
         android:layout_height="0dp"
+        android:contentDescription="@string/accessibility_bluetooth_device_settings_gear"
         app:layout_constraintStart_toEndOf="@+id/bluetooth_device_name"
         app:layout_constraintEnd_toEndOf="@+id/gear_icon_image"
         app:layout_constraintTop_toTopOf="parent"
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index 16aeb95..5d986e0 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -27,6 +27,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:paddingTop="24dp"
+        android:maxLines="1"
         android:ellipsize="end"
         android:gravity="center_vertical|center_horizontal"
         android:text="@string/quick_settings_bluetooth_label"
@@ -58,9 +59,12 @@
         style="@style/BluetoothTileDialog.Device"
         android:layout_width="0dp"
         android:layout_height="64dp"
+        android:maxLines="1"
+        android:ellipsize="end"
         android:gravity="center_vertical"
         android:layout_marginTop="4dp"
         android:text="@string/turn_on_bluetooth"
+        android:clickable="false"
         android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
         android:textSize="16sp"
         app:layout_constraintEnd_toStartOf="@+id/bluetooth_toggle"
@@ -84,53 +88,17 @@
         app:layout_constraintStart_toEndOf="@+id/bluetooth_toggle_title"
         app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle" />
 
-    <androidx.constraintlayout.widget.Group
-        android:id="@+id/pair_new_device_layout_group"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:visibility="gone"
-        app:constraint_referenced_ids="ic_add,pair_new_device_text" />
-
-    <ImageView
-        android:id="@+id/ic_add"
-        android:layout_width="24dp"
-        android:layout_height="24dp"
-        android:layout_marginStart="36dp"
-        android:gravity="center_vertical"
-        android:importantForAccessibility="no"
-        android:src="@drawable/ic_add"
-        app:layout_constraintBottom_toTopOf="@id/device_list"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toStartOf="@id/pair_new_device_text"
-        app:layout_constraintTop_toBottomOf="@id/bluetooth_toggle_title"
-        android:tint="?android:attr/textColorPrimary" />
-
-    <TextView
-        android:id="@+id/pair_new_device_text"
-        style="@style/BluetoothTileDialog.Device"
-        android:layout_width="0dp"
-        android:layout_height="@dimen/bluetooth_dialog_device_height"
-        android:gravity="center_vertical"
-        android:layout_marginStart="0dp"
-        android:paddingStart="20dp"
-        android:text="@string/pair_new_bluetooth_devices"
-        android:textSize="14sp"
-        android:textAppearance="@style/TextAppearance.Dialog.Title"
-        app:layout_constraintBottom_toTopOf="@id/device_list"
-        app:layout_constraintStart_toEndOf="@+id/ic_add"
-        app:layout_constraintTop_toBottomOf="@id/bluetooth_toggle_title"
-        app:layout_constraintEnd_toEndOf="parent" />
-
     <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/device_list"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:layout_marginTop="20dp"
         android:nestedScrollingEnabled="false"
         android:overScrollMode="never"
         android:scrollbars="vertical"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/pair_new_device_text"
+        app:layout_constraintTop_toBottomOf="@id/bluetooth_toggle"
         app:layout_constraintBottom_toTopOf="@+id/see_all_text" />
 
     <androidx.constraintlayout.widget.Group
@@ -148,7 +116,7 @@
         android:importantForAccessibility="no"
         android:gravity="center_vertical"
         android:src="@drawable/ic_arrow_forward"
-        app:layout_constraintBottom_toTopOf="@+id/done_button"
+        app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toStartOf="@id/see_all_text"
         app:layout_constraintTop_toBottomOf="@id/device_list" />
@@ -157,18 +125,59 @@
         android:id="@+id/see_all_text"
         style="@style/BluetoothTileDialog.Device"
         android:layout_width="0dp"
-        android:layout_height="@dimen/bluetooth_dialog_device_height"
+        android:layout_height="64dp"
+        android:maxLines="1"
+        android:ellipsize="end"
         android:gravity="center_vertical"
         android:layout_marginStart="0dp"
         android:paddingStart="20dp"
         android:text="@string/see_all_bluetooth_devices"
         android:textSize="14sp"
         android:textAppearance="@style/TextAppearance.Dialog.Title"
-        app:layout_constraintBottom_toTopOf="@+id/done_button"
+        app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text"
         app:layout_constraintStart_toEndOf="@+id/ic_arrow"
         app:layout_constraintTop_toBottomOf="@id/device_list"
         app:layout_constraintEnd_toEndOf="parent" />
 
+    <androidx.constraintlayout.widget.Group
+        android:id="@+id/pair_new_device_layout_group"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        app:constraint_referenced_ids="ic_add,pair_new_device_text" />
+
+    <ImageView
+        android:id="@+id/ic_add"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_marginStart="36dp"
+        android:gravity="center_vertical"
+        android:importantForAccessibility="no"
+        android:src="@drawable/ic_add"
+        app:layout_constraintBottom_toTopOf="@id/done_button"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/pair_new_device_text"
+        app:layout_constraintTop_toBottomOf="@id/see_all_text"
+        android:tint="?android:attr/textColorPrimary" />
+
+    <TextView
+        android:id="@+id/pair_new_device_text"
+        style="@style/BluetoothTileDialog.Device"
+        android:layout_width="0dp"
+        android:layout_height="64dp"
+        android:maxLines="1"
+        android:ellipsize="end"
+        android:gravity="center_vertical"
+        android:layout_marginStart="0dp"
+        android:paddingStart="20dp"
+        android:text="@string/pair_new_bluetooth_devices"
+        android:textSize="14sp"
+        android:textAppearance="@style/TextAppearance.Dialog.Title"
+        app:layout_constraintBottom_toTopOf="@id/done_button"
+        app:layout_constraintStart_toEndOf="@+id/ic_add"
+        app:layout_constraintTop_toBottomOf="@id/see_all_text"
+        app:layout_constraintEnd_toEndOf="parent" />
+
     <Button
         android:id="@+id/done_button"
         style="@style/Widget.Dialog.Button"
@@ -184,5 +193,5 @@
         android:text="@string/inline_done_button"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/see_all_text" />
+        app:layout_constraintTop_toBottomOf="@id/pair_new_device_text" />
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
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/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index ec006c5..16eba22 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -403,7 +403,7 @@
                     android:layout_height="wrap_content"
                     android:layout_weight="1"
                     android:layout_gravity="start|center_vertical"
-                    android:orientation="vertical">
+                    android:orientation="horizontal">
                     <Button
                         android:id="@+id/apm_button"
                         android:layout_width="wrap_content"
@@ -414,12 +414,7 @@
                         style="@style/Widget.Dialog.Button.BorderButton"
                         android:clickable="true"
                         android:focusable="true"/>
-                </LinearLayout>
 
-                <RelativeLayout
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:gravity="center_vertical">
                     <Button
                         android:id="@+id/share_wifi_button"
                         android:layout_width="wrap_content"
@@ -430,8 +425,14 @@
                         android:ellipsize="end"
                         android:clickable="true"
                         android:focusable="true"
-                        android:layout_alignParentLeft="true"
                         android:visibility="gone"/>
+                </LinearLayout>
+
+                <LinearLayout
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="16dp"
+                    android:layout_gravity="end|center_vertical">
                     <Button
                         android:id="@+id/done_button"
                         android:layout_width="wrap_content"
@@ -441,9 +442,8 @@
                         android:maxLines="1"
                         android:ellipsize="end"
                         android:clickable="true"
-                        android:focusable="true"
-                        android:layout_alignParentRight="true"/>
-                </RelativeLayout>
+                        android:focusable="true"/>
+                </LinearLayout>
             </LinearLayout>
 
         </LinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
index b00908f..c1bac31 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
@@ -15,7 +15,7 @@
   -->
 
 <!-- Extends Framelayout -->
-<com.android.systemui.statusbar.notification.row.FooterView
+<com.android.systemui.statusbar.notification.footer.ui.view.FooterView
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:app="http://schemas.android.com/apk/res-auto"
         android:layout_width="match_parent"
@@ -76,4 +76,4 @@
                 />
         </androidx.constraintlayout.widget.ConstraintLayout>
     </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
-</com.android.systemui.statusbar.notification.row.FooterView>
+</com.android.systemui.statusbar.notification.footer.ui.view.FooterView>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 937e97a..24846d9 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Stel versteknotasapp in Instellings"</string>
     <string name="install_app" msgid="5066668100199613936">"Installeer app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Sinkroniseer wedersyds na eksterne skerm?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Sinkroniseer skerm wedersyds"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Maak toe"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofoon en kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Onlangse appgebruik"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Sien onlangse toegang"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index de2fda4..23def28 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"በቅንብሮች ውስጥ ነባሪ የማስታወሻዎች መተግበሪያን ያቀናብሩ"</string>
     <string name="install_app" msgid="5066668100199613936">"መተግበሪያን ጫን"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ወደ ውጫዊ ማሳያ ይንጸባረቅ?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ማሳያን አንጸባርቅ"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"አሰናብት"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"ማይክሮፎን እና ካሜራ"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"የቅርብ ጊዜ የመተግበሪያ አጠቃቀም"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"የቅርብ ጊዜ መዳረሻን አሳይ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 79b671c..80d63a2 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"يمكنك ضبط تطبيق تدوين الملاحظات التلقائي في \"الإعدادات\"."</string>
     <string name="install_app" msgid="5066668100199613936">"تثبيت التطبيق"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"هل تريد بث محتوى جهازك على الشاشة الخارجية؟"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"بث المحتوى على الشاشة"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"إغلاق"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"الميكروفون والكاميرا"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"آخر استخدام في التطبيقات"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"عرض آخر استخدام في التطبيقات"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index ac30f18..c845773 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ছেটিঙত টোকাৰ ডিফ’ল্ট এপ্ ছেট কৰক"</string>
     <string name="install_app" msgid="5066668100199613936">"এপ্‌টো ইনষ্টল কৰক"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"বাহ্যিক ডিছপ্লে’লৈ মিৰ’ৰ কৰিবনে?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ডিছপ্লে’ মিৰ’ৰ কৰক"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"অগ্ৰাহ্য কৰক"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"মাইক্ৰ’ফ’ন আৰু কেমেৰা"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"শেহতীয়া এপৰ ব্যৱহাৰ"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"শেহতীয়া এক্সেছ চাওক"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 3ca61dc..5aa26ba 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ayarlarda defolt qeydlər tətbiqi ayarlayın"</string>
     <string name="install_app" msgid="5066668100199613936">"Tətbiqi quraşdırın"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Xarici displeyə əks etdirilsin?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Displeyi əks etdirin"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"İmtina edin"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon və kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Son tətbiq istifadəsi"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Son girişə baxın"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index b51d4b3..cd36884 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Podesite podrazumevanu aplikaciju za beleške u Podešavanjima"</string>
     <string name="install_app" msgid="5066668100199613936">"Instaliraj aplikaciju"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Želite li da preslikate na spoljnji ekran?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Preslikaj ekran"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Odbaci"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon i kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedavno koristila aplikacija"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Prikaži nedavni pristup"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 35a84b0..1b03c8b 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Задайце ў Наладах стандартную праграму для нататак"</string>
     <string name="install_app" msgid="5066668100199613936">"Усталяваць праграму"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Адлюстраваць на знешнім дысплеі?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Адлюстраваць дысплэй"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Закрыць"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Мікрафон і камера"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Нядаўна выкарыстоўваліся праграмамі"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Паглядзець нядаўні доступ"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 6b212e5..4ed1ad9 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Задайте стандартно приложение за бележки от настройките"</string>
     <string name="install_app" msgid="5066668100199613936">"Инсталиране на приложението"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Да се дублира ли на външния екран?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Огледално копиране на дисплея"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Отхвърляне"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон и камера"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Скорошно използване на приложението"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Вижте скорошния достъп"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 5b5ac73..426d38d 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"\'সেটিংস\' থেকে ডিফল্ট নোট নেওয়ার অ্যাপ সেট করুন"</string>
     <string name="install_app" msgid="5066668100199613936">"অ্যাপ ইনস্টল করুন"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"এক্সটার্নাল ডিসপ্লে আয়না?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ডিসপ্লে দেখান"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"বাতিল করুন"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"মাইক্রোফোন ও ক্যামেরা"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"সম্প্রতি ব্যবহার করা অ্যাপ"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"সাম্প্রতিক অ্যাক্সেস দেখুন"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 33330b7..4eed7b89 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Postavite zadanu aplikaciju za bilješke u Postavkama"</string>
     <string name="install_app" msgid="5066668100199613936">"Instaliraj aplikaciju"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Preslikati na vanjski ekran?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Preslikaj ekran"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Odbaci"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon i kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedavno korištenje aplikacije"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Prikaži nedavni pristup"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 45bb342..b55a79a 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defineix l\'aplicació de notes predeterminada a Configuració"</string>
     <string name="install_app" msgid="5066668100199613936">"Instal·la l\'aplicació"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vols replicar-ho a la pantalla externa?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Duplica la pantalla"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Ignora"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Micròfon i càmera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Ús recent de l\'aplicació"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Mostra l\'accés recent"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 0d7d23a..05b0419 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Výchozí aplikaci pro poznámky nastavíte v Nastavení"</string>
     <string name="install_app" msgid="5066668100199613936">"Nainstalovat aplikaci"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Zrcadlit na externí displej?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Zrcadlit displej"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Zavřít"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon a fotoaparát"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedávné použití aplikacemi"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Zobrazit nedávný přístup"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 4f6b88e..5971034 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Angiv standardapp til noter i Indstillinger"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vil du spejle til ekstern skærm?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Spejl skærm"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Luk"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon og kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Seneste brug af apps"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Se seneste adgang"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 07aa14f..c773f71 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standard-Notizen-App in den Einstellungen einrichten"</string>
     <string name="install_app" msgid="5066668100199613936">"App installieren"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Auf externen Bildschirm spiegeln?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Bildschirm spiegeln"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Schließen"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon &amp; Kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Kürzliche App-Nutzung"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Kürzliche Zugriffe ansehen"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 2a8e51c..b6c95aa 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ορίστε την προεπιλεγμένη εφαρμογή σημειώσεων στις Ρυθμίσεις"</string>
     <string name="install_app" msgid="5066668100199613936">"Εγκατάσταση εφαρμογής"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Κατοπτρισμός σε εξωτερική οθόνη;"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Κατοπτρισμός οθόνης"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Παράβλεψη"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Μικρόφωνο και Κάμερα"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Πρόσφατη χρήση εφαρμογής"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Εμφάνιση πρόσφατης πρόσβασης"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 8647adb..6c2b230 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
     <string name="install_app" msgid="5066668100199613936">"Install app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Dismiss"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microphone and Camera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Recent app use"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"See recent access"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 8647adb..6c2b230 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
     <string name="install_app" msgid="5066668100199613936">"Install app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Dismiss"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microphone and Camera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Recent app use"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"See recent access"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 8647adb..6c2b230 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
     <string name="install_app" msgid="5066668100199613936">"Install app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Dismiss"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microphone and Camera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Recent app use"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"See recent access"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index d3ea858..52514d2 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Configura la app de notas predeterminada en Configuración"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"¿Quieres duplicar a la pantalla externa?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Duplicar pantalla"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Descartar"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Micrófono y cámara"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso reciente en apps"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ver accesos recientes"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 284fad4..4a3c064 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Configura la aplicación de notas predeterminada en Ajustes"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar aplicación"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"¿Replicar en pantalla externa?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Replicar pantalla"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Cerrar"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Micrófono y cámara"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso reciente en aplicaciones"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ver acceso reciente"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 2b79ee5..08b489d 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Määrake seadetes märkmete vaikerakendus."</string>
     <string name="install_app" msgid="5066668100199613936">"Installi rakendus"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Kas peegeldada välisekraanile?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Ekraani peegeldamine"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Loobu"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon ja kaamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Rakenduste hiljutine kasutamine"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Kuva hiljutine juurdepääs"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 68698ea..19495bc 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -676,8 +676,8 @@
     <string name="group_system_go_back" msgid="8838454003680364227">"Atzera: itzuli aurreko egoerara (atzera egiteko botoia)"</string>
     <string name="group_system_access_home_screen" msgid="1857344316928441909">"Atzitu hasierako pantaila"</string>
     <string name="group_system_overview_open_apps" msgid="6897128761003265350">"Ikusi irekitako aplikazioen ikuspegi orokorra"</string>
-    <string name="group_system_cycle_forward" msgid="9202444850838205990">"Joan azken aplikazioetako batetik bestera (aurrera)"</string>
-    <string name="group_system_cycle_back" msgid="5163464503638229131">"Joan azken aplikazioetako batetik bestera (atzera)"</string>
+    <string name="group_system_cycle_forward" msgid="9202444850838205990">"Joan azkenaldian erabilitako aplikazio batetik bestera (aurrera)"</string>
+    <string name="group_system_cycle_back" msgid="5163464503638229131">"Joan azkenaldian erabilitako aplikazio batetik bestera (atzera)"</string>
     <string name="group_system_access_all_apps_search" msgid="488070738028991753">"Atzitu aplikazio guztien zerrenda eta bilatu (adibidez, bilatzeko aukeraren edo Exekutatzeko tresna aplikazioaren bidez)"</string>
     <string name="group_system_hide_reshow_taskbar" msgid="3809304065624351131">"Ezkutatu eta erakutsi (berriro) zereginen barra"</string>
     <string name="group_system_access_system_settings" msgid="7961639365383008053">"Atzitu sistemaren ezarpenak"</string>
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ezarri oharren aplikazio lehenetsia ezarpenetan"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalatu aplikazioa"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Kanpoko pantailan islatu nahi duzu?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Islatu pantaila"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Baztertu"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofonoa eta kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Aplikazioen azken erabilera"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ikusi azkenaldiko sarbidea"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 7199240..68f6c1d 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"برنامه پیش‌فرض یادداشت را در «تنظیمات» تنظیم کنید"</string>
     <string name="install_app" msgid="5066668100199613936">"نصب برنامه"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"در نمایشگر خارجی پخش شود؟"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"بازتاباندن صفحه‌نمایش"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"بستن"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"میکروفون و دوربین"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"استفاده اخیر از برنامه"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"دیدن دسترسی اخیر"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 073ea60..934abad 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Aseta oletusmuistiinpanosovellus Asetuksista"</string>
     <string name="install_app" msgid="5066668100199613936">"Asenna sovellus"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Peilataanko ulkoiselle näytölle?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Peilaa näyttö"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Ohita"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofoni ja kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Sovellusten viimeaikainen käyttö"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Katso viimeaikainen käyttö"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 52bcd76..fe90569 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Définir l\'application de prise de notes par défaut dans les Paramètres"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer l\'application"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Dupliquer l\'écran sur un moniteur externe?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Dupliquer l\'écran"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Fermer"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microphone et appareil photo"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Utilisation récente par les applications"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Afficher l\'accès récent"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 4362d3c..27a4bb6 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Définir une appli de notes par défaut dans les paramètres"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer l\'appli"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirroring sur écran externe ?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Dupliquer l\'écran"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Fermer"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Micro et caméra"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Utilisation récente par les applis"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Consulter les accès récents"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 1b836a8..2056c2f 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Establece a aplicación de notas predeterminada en Configuración"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar aplicación"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Queres proxectar contido nunha pantalla externa?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Replicar pantalla"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Pechar"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Micrófono e cámara"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso recente por parte de aplicacións"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ver acceso recente"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 132ee2e..84be50a 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"સેટિંગમાં નોંધની ડિફૉલ્ટ ઍપ સેટ કરો"</string>
     <string name="install_app" msgid="5066668100199613936">"ઍપ ઇન્સ્ટૉલ કરો"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"શું બાહ્ય ડિસ્પ્લે પર મિરર કરીએ?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"મિરર ડિસ્પ્લે"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"છોડી દો"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"માઇક્રોફોન અને કૅમેરા"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"તાજેતરનો ઍપનો વપરાશ"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"તાજેતરનો ઍક્સેસ મેનેજ કરો"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index f942de5..2b0019f 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिंग में जाकर, नोट लेने की सुविधा देने वाले ऐप्लिकेशन को डिफ़ॉल्ट के तौर पर सेट करें"</string>
     <string name="install_app" msgid="5066668100199613936">"ऐप्लिकेशन इंस्टॉल करें"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"बाहरी डिसप्ले को अन्य डिवाइस पर दिखाना है?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"मिरर डिसप्ले"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"खारिज करें"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"माइक्रोफ़ोन और कैमरा"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"हाल ही में इस्तेमाल करने वाला ऐप्लिकेशन"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"हाल में ऐक्सेस करने वाले ऐप"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index f7c68e1..23899fc 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Postavite zadanu aplikaciju za bilješke u postavkama"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalacija"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Želite li zrcaliti na vanjski zaslon?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Zrcaljenje zaslona"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Odbaci"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon i kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedavna upotreba aplikacije"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Pogledajte nedavni pristup"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index c1b38d9..e5b17d9 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Állítson be alapértelmezett jegyzetkészítő alkalmazást a Beállításokban"</string>
     <string name="install_app" msgid="5066668100199613936">"Alkalmazás telepítése"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Tükrözi a kijelzőt a külső képernyőre?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Kijelző tükrözése"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Elvetés"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon és kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Legutóbbi alkalmazáshasználat"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Legutóbbi hozzáférés"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 7139a1f..4a1c291 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Կարգավորեք նշումների կանխադրված հավելված Կարգավորումներում"</string>
     <string name="install_app" msgid="5066668100199613936">"Տեղադրել հավելվածը"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Հայելապատճենե՞լ արտաքին էկրանին"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Հայելապատճենել էկրանը"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Փակել"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Խոսափող և տեսախցիկ"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Հավելվածի վերջին օգտագործումը"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Տեսնել վերջին օգտագործումը"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 44aafde..18a7668 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setel aplikasi catatan default di Setelan"</string>
     <string name="install_app" msgid="5066668100199613936">"Instal aplikasi"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Cerminkan ke layar eksternal?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Cerminkan layar"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Tutup"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon &amp; Kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Penggunaan aplikasi baru-baru ini"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Lihat akses terbaru"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 1fda572..70bed46 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Stilltu sjálfgefið glósuforrit í stillingunum"</string>
     <string name="install_app" msgid="5066668100199613936">"Setja upp forrit"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Spegla yfir á ytri skjá?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Spegla skjá"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Hunsa"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Hljóðnemi og myndavél"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nýlega notað af forriti"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Sjá nýlegan aðgang"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index ed8885f..baffdb9 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Imposta l\'app per le note predefinita nelle Impostazioni"</string>
     <string name="install_app" msgid="5066668100199613936">"Installa app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vuoi eseguire il mirroring al display esterno?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Esegui il mirroring del display"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Chiudi"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfono e fotocamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso recente da app"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Vedi accesso recente"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 51f5452..968a982 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"צריך להגדיר את אפליקציית ברירת המחדל לפתקים ב\'הגדרות\'"</string>
     <string name="install_app" msgid="5066668100199613936">"התקנת האפליקציה"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"לשקף למסך חיצוני?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"תצוגת מראה"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"סגירה"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"מיקרופון ומצלמה"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"נעשה שימוש לאחרונה באפליקציות"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"צפייה בהרשאות הגישה האחרונות"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index e77aed9..798d42a 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"[設定] でデフォルトのメモアプリを設定してください"</string>
     <string name="install_app" msgid="5066668100199613936">"アプリをインストール"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"外部ディスプレイにミラーリングしますか?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ディスプレイをミラーリングする"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"閉じる"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"マイクとカメラ"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"最近のアプリの使用状況"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"最近のアクセスを表示"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 8d7deec..f21a2a4 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"დააყენეთ ნაგულისხმევი შენიშვნების აპი პარამეტრებში"</string>
     <string name="install_app" msgid="5066668100199613936">"აპის ინსტალაცია"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"აირეკლოს გარე ეკრანზე?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ეკრანის არეკვლა"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"დახურვა"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"მიკროფონი და კამერა"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"აპის ბოლოდროინდელი გამოყენება"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ბოლო წვდომის ნახვა"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index fc4fe8a..746c02e 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Параметрлерден әдепкі жазба қолданбасын орнатыңыз."</string>
     <string name="install_app" msgid="5066668100199613936">"Қолданбаны орнату"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Сыртқы экран арқылы да көрсету керек пе?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Айна дисплей"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Қабылдамау"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон және камера"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Соңғы рет қолданбаның датчикті пайдалануы"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Соңғы рет пайдаланғандар"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 10c291d..9f3b991 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"កំណត់កម្មវិធីកំណត់ចំណាំលំនាំដើមនៅក្នុងការកំណត់"</string>
     <string name="install_app" msgid="5066668100199613936">"ដំឡើង​កម្មវិធី"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"បញ្ចាំងទៅ​ឧបករណ៍បញ្ចាំង​ខាងក្រៅឬ?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"បញ្ចាំងទៅផ្ទាំងអេក្រង់"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"ច្រានចោល"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"មីក្រូហ្វូន និងកាមេរ៉ា"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"ការប្រើប្រាស់កម្មវិធីថ្មីៗនេះ"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"មើលការចូលប្រើនាពេលថ្មីៗនេះ"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 7716980..79eef72 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಡೀಫಾಲ್ಟ್ ಟಿಪ್ಪಣಿಗಳ ಆ್ಯಪ್ ಅನ್ನು ಸೆಟ್ ಮಾಡಿ"</string>
     <string name="install_app" msgid="5066668100199613936">"ಆ್ಯಪ್ ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ಬಾಹ್ಯ ಡಿಸ್‌ಪ್ಲೇಗೆ ಪ್ರತಿಬಿಂಬಿಸಬೇಕೆ?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ಮಿರರ್ ಡಿಸ್‌ಪ್ಲೇ"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"ವಜಾಗೊಳಿಸಿ"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"ಮೈಕ್ರೊಫೋನ್ ಮತ್ತು ಕ್ಯಾಮರಾ"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"ಇತ್ತೀಚಿನ ಆ್ಯಪ್ ಬಳಕೆ"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ಇತ್ತೀಚಿನ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ನೋಡಿ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index c2fbf09..7985e4c 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"설정에서 기본 메모 앱 설정"</string>
     <string name="install_app" msgid="5066668100199613936">"앱 설치"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"외부 디스플레이로 미러링하시겠습니까?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"디스플레이 미러링"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"닫기"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"마이크 및 카메라"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"최근 앱 사용"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"최근 액세스 보기"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 8ab4cb7..ded1f75 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Параметрлерден демейки кыска жазуулар колдонмосун тууралаңыз"</string>
     <string name="install_app" msgid="5066668100199613936">"Колдонмону орнотуу"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Тышкы экранга чыгарасызбы?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Тышкы экран"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Жабуу"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон жана камера"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Жакында колдонмолордо иштетилген"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Акыркы пайдалануусун көрүү"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index a1585f2..1e61b8a 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ຕັ້ງຄ່າແອັບຈົດບັນທຶກເລີ່ມຕົ້ນໃນການຕັ້ງຄ່າ"</string>
     <string name="install_app" msgid="5066668100199613936">"ຕິດຕັ້ງແອັບ"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ສາຍໃສ່ຈໍສະແດງຜົນພາຍນອກບໍ?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ຈໍສະແດງຜົນແບບສະທ້ອນ"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"ປິດໄວ້"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"ໄມໂຄຣໂຟນ ແລະ ກ້ອງຖ່າຍຮູບ"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"ການໃຊ້ແອັບຫຼ້າສຸດ"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ເບິ່ງສິດເຂົ້າເຖິງຫຼ້າສຸດ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index c5f649c..06c1369 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nustatykite numatytąją užrašų programą Nustatymuose"</string>
     <string name="install_app" msgid="5066668100199613936">"Įdiegti programą"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Bendrinti ekrano vaizdą išoriniame ekrane?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Bendrinti ekrano vaizdą"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Atsisakyti"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofonas ir fotoaparatas"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Pastarasis programos naudojimas"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Žr. pastarąją prieigą"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index d493609..fdf3028 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Iestatījumos iestatiet noklusējuma piezīmju lietotni."</string>
     <string name="install_app" msgid="5066668100199613936">"Instalēt lietotni"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vai spoguļot ārējā displejā?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Spoguļot displeju"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Nerādīt"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofons un kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nesen izmantoja lietotnes"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Skatīt neseno piekļuvi"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 46193b3..80ef7bb 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Поставете стандардна апликација за белешки во „Поставки“"</string>
     <string name="install_app" msgid="5066668100199613936">"Инсталирајте ја апликацијата"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Да се синхронизира на надворешниот екран?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Пресликај екран"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Отфрли"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон и камера"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Неодамнешно користење на апликација"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Видете го скорешниот пристап"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index d631538..5b1c80f 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ക്രമീകരണത്തിൽ കുറിപ്പുകൾക്കുള്ള ഡിഫോൾട്ട് ആപ്പ് സജ്ജീകരിക്കുക"</string>
     <string name="install_app" msgid="5066668100199613936">"ആപ്പ് ഇൻസ്റ്റാൾ ചെയ്യൂ"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ബാഹ്യ ഡിസ്‌പ്ലേയിലേക്ക് മിറർ ചെയ്യണോ?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"മിറർ ഡിസ്‌പ്ലേ"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"ഡിസ്‌മിസ് ചെയ്യുക"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"മൈക്രോഫോണും ക്യാമറയും"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"അടുത്തിടെയുള്ള ആപ്പ് ഉപയോഗം"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"അടുത്തിടെയുള്ള ആക്‌സസ് കാണുക"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index b16b22a..3f65931 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Тохиргоонд тэмдэглэлийн өгөгдмөл апп тохируулна уу"</string>
     <string name="install_app" msgid="5066668100199613936">"Аппыг суулгах"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Гадны дэлгэцэд тусгал үүсгэх үү?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Дэлгэцийн тусгал үүсгэх"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Хаах"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон болон камер"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Аппын саяхны ашиглалт"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Саяхны хандалтыг харах"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 16fac23..238aaca 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिंग्ज मध्ये डीफॉल्ट टिपा अ‍ॅप सेट करा"</string>
     <string name="install_app" msgid="5066668100199613936">"अ‍ॅप इंस्टॉल करा"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"बाह्य डिस्प्लेवर मिरर करायचे आहे का?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"डिस्प्ले मिरर करा"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"डिसमिस करा"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"मायक्रोफोन आणि कॅमेरा"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"अलीकडील अ‍ॅप वापर"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"अलीकडील अ‍ॅक्सेस पहा"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 7cafdcd..8c764f1 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Tetapkan apl nota lalai dalam Tetapan"</string>
     <string name="install_app" msgid="5066668100199613936">"Pasang apl"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Paparkan pada paparan luaran?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Segerakkan paparan"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Ketepikan"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon &amp; Kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Penggunaan apl terbaharu"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Lihat akses terbaharu"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 620124e..883b9e9 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ဆက်တင်များတွင် မူရင်းမှတ်စုများအက်ပ် သတ်မှတ်ပါ"</string>
     <string name="install_app" msgid="5066668100199613936">"အက်ပ် ထည့်သွင်းရန်"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ပြင်ပဖန်သားပြင်သို့ စကရင်ပွားမလား။"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ဖန်သားပြင်ကို စကရင်ပွားရန်"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"ပယ်ရန်"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"မိုက်ခရိုဖုန်းနှင့် ကင်မရာ"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"လတ်တလော အက်ပ်အသုံးပြုမှု"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"လတ်တလောအသုံးပြုမှုကို ကြည့်ရန်"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 722cc12..53a4c52 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Du kan velge en standardapp for notater i Innstillinger"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer appen"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vil du speile til en ekstern skjerm?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Speil skjermen"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Lukk"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon og kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nylig appbruk"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Se nylig tilgang"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 74e5c6f..3b31b3a 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिङमा गई नोट बनाउने डिफल्ट एप तोक्नुहोस्"</string>
     <string name="install_app" msgid="5066668100199613936">"एप इन्स्टल गर्नुहोस्"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"बाह्य डिस्प्लेमा मिरर गर्ने हो?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"डिस्प्ले मिरर गर्नुहोस्"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"खारेज गर्नुहोस्"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"माइक्रोफोन तथा क्यामेरा"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"एपको हालसालैको प्रयोग"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"हालसालै एक्सेस गर्ने एप हेर्नुहोस्"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 6d97a9f..664af5e 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standaard notitie-app instellen in Instellingen"</string>
     <string name="install_app" msgid="5066668100199613936">"App installeren"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Spiegelen naar extern scherm?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Scherm spiegelen"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Sluiten"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfoon en camera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Recent app-gebruik"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Recente toegang bekijken"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index a6e14f9..db9e0c5 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ସେଟିଂସରେ ଡିଫଲ୍ଟ ନୋଟ୍ସ ଆପ ସେଟ କରନ୍ତୁ"</string>
     <string name="install_app" msgid="5066668100199613936">"ଆପ ଇନଷ୍ଟଲ କରନ୍ତୁ"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ଏକ୍ସଟର୍ନଲ ଡିସପ୍ଲେକୁ ମିରର କରିବେ?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ଡିସପ୍ଲେ ମିରର କରନ୍ତୁ"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"ଖାରଜ କରନ୍ତୁ"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"ମାଇକ୍ରୋଫୋନ ଏବଂ କେମେରା"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"ବର୍ତ୍ତମାନର ଆପ ବ୍ୟବହାର"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ବର୍ତ୍ତମାନର ଆକ୍ସେସ ଦେଖନ୍ତୁ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index faca7b1..591c5e7 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਨੋਟ ਐਪ ਨੂੰ ਸੈੱਟ ਕਰੋ"</string>
     <string name="install_app" msgid="5066668100199613936">"ਐਪ ਸਥਾਪਤ ਕਰੋ"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ਕੀ ਬਾਹਰੀ ਡਿਸਪਲੇ \'ਤੇ ਪ੍ਰਤਿਬਿੰਬਿਤ ਕਰਨਾ ਹੈ?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ਡਿਸਪਲੇ ਨੂੰ ਪ੍ਰਤਿਬਿੰਬਿਤ ਕਰੋ"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"ਖਾਰਜ ਕਰੋ"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਅਤੇ ਕੈਮਰਾ"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"ਹਾਲ ਹੀ ਵਿੱਚ ਵਰਤੀ ਗਈ ਐਪ"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ਹਾਲੀਆ ਪਹੁੰਚ ਦੇਖੋ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 3bb539a..1951221 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ustaw domyślną aplikację do obsługi notatek w Ustawieniach"</string>
     <string name="install_app" msgid="5066668100199613936">"Zainstaluj aplikację"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Powielić na wyświetlaczu zewnętrznym?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Powielaj obraz"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Zamknij"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon i aparat"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Aplikacje korzystające w ostatnim czasie"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Zobacz ostatni dostęp"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 8f93b73..693a3a1 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defina o app de notas padrão nas Configurações"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar o app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para a tela externa?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Espelhar tela"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Dispensar"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfone e câmera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso recente do app"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Consultar acessos recentes"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 02ee098..c4f1f67 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Predefina a app de notas nas Definições"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para o ecrã externo?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Espelhar ecrã"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Ignorar"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfone e câmara"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Utilização recente da app"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ver acesso recente"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 8f93b73..693a3a1 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defina o app de notas padrão nas Configurações"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar o app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para a tela externa?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Espelhar tela"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Dispensar"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfone e câmera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso recente do app"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Consultar acessos recentes"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 14cdeac..a942332 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setează aplicația prestabilită de note din Setări"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalează aplicația"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Oglindești pe ecranul extern?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Afișare în oglindă"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Închide"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfon și cameră"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Utilizare recentă în aplicații"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Vezi accesarea recentă"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index c5379c2..68601d6 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Задайте стандартное приложение для заметок в настройках."</string>
     <string name="install_app" msgid="5066668100199613936">"Установить приложение"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Дублировать на внешний дисплей?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Дублировать дисплей"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Закрыть"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон и камера"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Недавнее использование приложениями"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Посмотреть недавний доступ"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index d53f0eb..86cb3c3 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"සැකසීම් තුළ පෙරනිමි සටහන් යෙදුම සකසන්න"</string>
     <string name="install_app" msgid="5066668100199613936">"යෙදුම ස්ථාපනය කරන්න"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"බාහිර සංදර්ශකයට දර්පණය කරන්න ද?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"සංදර්ශකය දර්පණය කරන්න"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"අස් කරන්න"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"මයික්‍රොෆෝනය සහ කැමරාව"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"මෑත යෙදුම් භාවිතය"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"මෑත ප්‍රවේශය බලන්න"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 7fa0d07..56b9f75 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nastavte predvolenú aplikáciu na poznámky v Nastaveniach"</string>
     <string name="install_app" msgid="5066668100199613936">"Inštalovať aplikáciu"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Chcete zrkadliť na externú obrazovku?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Zrkadliť obrazovku"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Zavrieť"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofón a fotoaparát"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedávne využitie aplikácie"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Zobraziť nedávny prístup"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 87c18f4..b72f750 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nastavite privzeto aplikacijo za zapiske v nastavitvah."</string>
     <string name="install_app" msgid="5066668100199613936">"Namesti aplikacijo"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Želite zrcaliti v zunanji zaslon?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Zrcali zaslon"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Opusti"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon in fotoaparat"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedavna uporaba v aplikacijah"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ogled nedavnih dostopov"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 3e1471a..92f6f9c 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Cakto aplikacionin e parazgjedhur të shënimeve te \"Cilësimet\""</string>
     <string name="install_app" msgid="5066668100199613936">"Instalo aplikacionin"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Të pasqyrohet në ekranin e jashtëm?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Pasqyro ekranin"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Hiq"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofoni dhe kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Përdorimi i fundit i aplikacionit"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Shiko qasjen e fundit"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 07897d7..7c0d9be 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Подесите подразумевану апликацију за белешке у Подешавањима"</string>
     <string name="install_app" msgid="5066668100199613936">"Инсталирај апликацију"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Желите ли да пресликате на спољњи екран?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Пресликај екран"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Одбаци"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон и камера"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Недавно користила апликација"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Прикажи недавни приступ"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index aee168e..323ce11f 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ställ in en standardapp för anteckningar i inställningarna"</string>
     <string name="install_app" msgid="5066668100199613936">"Installera appen"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vill du spegla till extern skärm?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Spegla skärm"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Ignorera"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon och kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Senaste appanvändning"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Se senaste åtkomst"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index be3badd..304910a 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Teua programu chaguomsingi ya madokezo katika Mipangilio"</string>
     <string name="install_app" msgid="5066668100199613936">"Sakinisha programu"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Ungependa kuonyesha kwenye skrini ya nje?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Akisi skrini"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Ondoa"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Maikrofoni na Kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Matumizi ya programu hivi majuzi"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Angalia ufikiaji wa majuzi"</string>
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-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 785c4d5..3ac5e2e 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"குறிப்பு எடுப்பதற்கான இயல்புநிலை ஆப்ஸை அமைப்புகளில் அமையுங்கள்"</string>
     <string name="install_app" msgid="5066668100199613936">"ஆப்ஸை நிறுவுங்கள்"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"வெளிப்புறக் காட்சிக்கு மிரர் செய்யவா?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"டிஸ்பிளேயை மிரர் செய்"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"வேண்டாம்"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"மைக்ரோஃபோனும் கேமராவும்"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"சமீபத்திய ஆப்ஸ் பயன்பாடு"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"சமீபத்திய அணுகலைக் காட்டு"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 96d77b7..0526f95 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"సెట్టింగ్‌లలో ఆటోమేటిక్‌గా ఉండేలా ఒక నోట్స్ యాప్‌ను సెట్ చేసుకోండి"</string>
     <string name="install_app" msgid="5066668100199613936">"యాప్‌ను ఇన్‌స్టాల్ చేయండి"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"బాహ్య డిస్‌ప్లే‌ను మిర్రర్ చేయాలా?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"మిర్రర్ డిస్‌ప్లే"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"విస్మరించండి"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"మైక్రోఫోన్ &amp; కెమెరా"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"ఇటీవలి యాప్ వినియోగం"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ఇటీవలి యాక్సెస్‌ను చూడండి"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index f8fbfbe..8949360 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"กำหนดแอปการจดบันทึกเริ่มต้นในการตั้งค่า"</string>
     <string name="install_app" msgid="5066668100199613936">"ติดตั้งแอป"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"มิเรอร์ไปยังจอแสดงผลภายนอกไหม"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"มิเรอร์จอแสดงผล"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"ปิด"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"ไมโครโฟนและกล้อง"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"การใช้แอปครั้งล่าสุด"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ดูการเข้าถึงล่าสุด"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 435398d..e5c11df 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Magtakda ng default na app sa pagtatala sa Mga Setting"</string>
     <string name="install_app" msgid="5066668100199613936">"I-install ang app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"I-mirror sa external na display?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"I-mirror ang display"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"I-dismiss"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikropono at Camera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Kamakailang paggamit ng app"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Tingnan ang kamakailang access"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index d0ddbec..3552d92 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ayarlar\'ı kullanarak varsayılan notlar ayarlayın"</string>
     <string name="install_app" msgid="5066668100199613936">"Uygulamayı yükle"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Harici ekrana yansıtılsın mı?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Ekranı yansıt"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Kapat"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon ve Kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Son uygulama kullanımı"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Son erişimi göster"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 1489dc7..2e6aea9 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Призначте стандартний додаток для нотаток у налаштуваннях"</string>
     <string name="install_app" msgid="5066668100199613936">"Установити додаток"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Дублювати на зовнішньому екрані?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Дублювати екран"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Закрити"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Мікрофон і камера"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Нещодавнє використання додатками"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Переглянути нещодавній доступ"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 9d69a5e..2e2fc41 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ترتیبات میں ڈیفالٹ نوٹس ایپ سیٹ کریں"</string>
     <string name="install_app" msgid="5066668100199613936">"ایپ انسٹال کریں"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"بیرونی ڈسپلے پر مرر کریں؟"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"ڈسپلے کو دو طرفہ مطابقت پذیر بنائیں"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"برخاست کریں"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"مائیکروفون اور کیمرا"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"حالیہ ایپ کا استعمال"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"حالیہ رسائی دیکھیں"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 31b216b..a8654d5 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standart qaydlar ilovasini Sozlamalar orqali tanlang"</string>
     <string name="install_app" msgid="5066668100199613936">"Ilovani oʻrnatish"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Tashqi displeyda aks ettirilsinmi?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Displeyni akslantirish"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Yopish"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon va kamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Ilovadan oxirgi foydalanish"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Oxirgi ruxsatni koʻrish"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 814e0a6..5443745 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Đặt ứng dụng ghi chú mặc định trong phần Cài đặt"</string>
     <string name="install_app" msgid="5066668100199613936">"Cài đặt ứng dụng"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Đồng bộ hoá hai chiều sang màn hình ngoài?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Phản chiếu màn hình"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Đóng"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Micrô và máy ảnh"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Hoạt động sử dụng gần đây của ứng dụng"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Xem hoạt động truy cập gần đây"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 8b2caf0..44c163b 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"在设置中设置默认记事应用"</string>
     <string name="install_app" msgid="5066668100199613936">"安装应用"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"镜像到外接显示屏?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"镜像显示"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"关闭"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"麦克风和摄像头"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"近期应用对手机传感器的使用情况"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"查看近期使用情况"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 8a160c3..f45b2fe 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"在「設定」中指定預設筆記應用程式"</string>
     <string name="install_app" msgid="5066668100199613936">"安裝應用程式"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"要鏡像投射至外部顯示屏嗎?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"鏡像顯示"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"關閉"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"麥克風和相機"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"近期應用程式使用情況"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"查看近期存取記錄"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 38b81a0..5a5bb9d 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"在「設定」中指定預設記事應用程式"</string>
     <string name="install_app" msgid="5066668100199613936">"安裝應用程式"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"要以鏡像方式投放至外部螢幕嗎?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"鏡像顯示"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"關閉"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"麥克風和相機"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"最近曾使用感應器的應用程式"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"查看近期存取記錄"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index fc70417..8b6bfae 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -1183,10 +1183,8 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setha i-app yamanothi azenzakalelayo Kumsethingi"</string>
     <string name="install_app" msgid="5066668100199613936">"Faka i-app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Fanisa nesibonisi sangaphandle?"</string>
-    <!-- no translation found for mirror_display (2515262008898122928) -->
-    <skip />
-    <!-- no translation found for dismiss_dialog (2195508495854675882) -->
-    <skip />
+    <string name="mirror_display" msgid="2515262008898122928">"Isibonisi sokufanisa"</string>
+    <string name="dismiss_dialog" msgid="2195508495854675882">"Chitha"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Imakrofoni Nekhamera"</string>
     <string name="privacy_dialog_summary" msgid="2458769652125995409">"Ukusetshenziswa kwakamuva kwe-app"</string>
     <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Bona ukufinyelela kwakamuva"</string>
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 0ee5da2..ef0053d 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>
@@ -760,6 +766,8 @@
     <dimen name="keyguard_clock_switch_y_shift">14dp</dimen>
     <!-- When large clock is showing, offset the smartspace by this amount -->
     <dimen name="keyguard_smartspace_top_offset">12dp</dimen>
+    <!-- The amount to translate lockscreen elements on the GONE->AOD transition -->
+    <dimen name="keyguard_enter_from_top_translation_y">-100dp</dimen>
 
     <dimen name="notification_scrim_corner_radius">32dp</dimen>
 
@@ -846,12 +854,17 @@
     <!-- Amount the button should shake when it's not long-pressed for long enough. -->
     <dimen name="keyguard_affordance_shake_amplitude">8dp</dimen>
 
-    <dimen name="keyguard_affordance_horizontal_offset">32dp</dimen>
+    <dimen name="keyguard_affordance_horizontal_offset">16dp</dimen>
     <dimen name="keyguard_affordance_vertical_offset">32dp</dimen>
     <!-- Value should be at least sum of 'keyguard_affordance_width' +
          'keyguard_affordance_horizontal_offset' -->
     <dimen name="keyguard_indication_area_padding">82dp</dimen>
 
+    <!-- The width/padding of the communal tutorial indicator on keyguard. -->
+    <dimen name="communal_tutorial_indicator_fixed_width">168dp</dimen>
+    <dimen name="communal_tutorial_indicator_padding">24dp</dimen>
+    <dimen name="communal_tutorial_indicator_horizontal_offset">32dp</dimen>
+
     <!-- The width/height of the unlock icon view on keyguard. -->
     <dimen name="keyguard_lock_height">42dp</dimen>
     <dimen name="keyguard_lock_padding">20dp</dimen>
@@ -1350,6 +1363,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/ids.xml b/packages/SystemUI/res/values/ids.xml
index 81101d8..05f4334 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -222,12 +222,14 @@
     <item type="id" name="lock_icon" />
     <item type="id" name="lock_icon_bg" />
     <item type="id" name="burn_in_layer" />
+    <item type="id" name="communal_tutorial_indicator" />
 
     <!-- Privacy dialog -->
     <item type="id" name="privacy_dialog_close_app_button" />
     <item type="id" name="privacy_dialog_manage_app_button" />
 
     <!-- Communal mode -->
+    <item type="id" name="communal_hub" />
     <item type="id" name="communal_widget_wrapper" />
 
     <!-- Values assigned to the views in Biometrics Prompt -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a2637d5..7a6d29a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -209,6 +209,8 @@
     <string name="screenshot_saved_title">Screenshot saved</string>
     <!-- Notification title displayed when we fail to take a screenshot. [CHAR LIMIT=50] -->
     <string name="screenshot_failed_title">Couldn\'t save screenshot</string>
+    <!-- Appended to the notification content when a screenshot failure happens on an external display. [CHAR LIMIT=50] -->
+    <string name="screenshot_failed_external_display_indication">External Display</string>
     <!-- Notification text displayed when we fail to save a screenshot due to locked storage. [CHAR LIMIT=100] -->
     <string name="screenshot_failed_to_save_user_locked_text">Device must be unlocked before screenshot can be saved</string>
     <!-- Notification text displayed when we fail to save a screenshot for unknown reasons. [CHAR LIMIT=100] -->
@@ -711,8 +713,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] -->
@@ -1043,6 +1045,9 @@
     <!-- Indication on the keyguard that is shown when the device is dock charging. [CHAR LIMIT=80]-->
     <string name="keyguard_indication_charging_time_dock"><xliff:g id="percentage" example="20%">%2$s</xliff:g> • Charging • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>
 
+    <!-- Indicator shown to start the communal tutorial. [CHAR LIMIT=100] -->
+    <string name="communal_tutorial_indicator_text">Click on the arrow button to start the communal tutorial</string>
+
     <!-- Related to user switcher --><skip/>
 
     <!-- Accessibility label for the button that opens the user switcher. -->
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/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
index 5d036fb..b44bf39 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
@@ -71,7 +71,6 @@
 
     private AnimatedVectorDrawable mAnimatedDrawable;
     private boolean mIsShowing;
-    private boolean mCanShow = true;
     private int mDisplayRotation;
 
     private boolean mIsTaskbarVisible = false;
@@ -150,7 +149,7 @@
 
     @Override
     public boolean show() {
-        if (!mCanShow || mIsShowing) {
+        if (mIsShowing) {
             return false;
         }
 
@@ -222,14 +221,6 @@
     }
 
     @Override
-    public void setCanShowRotationButton(boolean canShow) {
-        mCanShow = canShow;
-        if (!mCanShow) {
-            hide();
-        }
-    }
-
-    @Override
     public void onTaskbarStateChanged(boolean taskbarVisible, boolean taskbarStashed) {
         mIsTaskbarVisible = taskbarVisible;
         mIsTaskbarStashed = taskbarStashed;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButton.java
index 89f71eb..42dda0a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButton.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButton.java
@@ -36,7 +36,6 @@
     default boolean isVisible() {
         return false;
     }
-    default void setCanShowRotationButton(boolean canShow) {}
     default void onTaskbarStateChanged(boolean taskbarVisible, boolean taskbarStashed) {}
     default void updateIcon(int lightIconColor, int darkIconColor) { }
     default void setOnClickListener(View.OnClickListener onClickListener) { }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 80040a3..631423e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -155,6 +155,26 @@
         }
     }
 
+
+    /**
+     * Requests for a new snapshot to be taken for the given task, stores it in the cache, and
+     * returns a {@link ThumbnailData} with the result.
+     */
+    @NonNull
+    public ThumbnailData takeTaskThumbnail(int taskId) {
+        TaskSnapshot snapshot = null;
+        try {
+            snapshot = getService().takeTaskSnapshot(taskId, /* updateCache= */ true);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to take task snapshot", e);
+        }
+        if (snapshot != null) {
+            return new ThumbnailData(snapshot);
+        } else {
+            return new ThumbnailData();
+        }
+    }
+
     /**
      * Removes the outdated snapshot of home task.
      *
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 1e7222d..88b9c02 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -233,6 +233,11 @@
                 runner.onAnimationCancelled();
                 finishRunnable.run();
             }
+
+            @Override
+            public void onTransitionConsumed(IBinder iBinder, boolean aborted)
+                    throws RemoteException {
+            }
         };
     }
 }
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/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index eb7a735..e47d36f 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -15,6 +15,8 @@
  */
 package com.android.keyguard
 
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
@@ -34,9 +36,10 @@
 import com.android.systemui.customization.R
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.DisplaySpecific
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.DOZING_MIGRATION_1
+import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW
 import com.android.systemui.flags.Flags.REGION_SAMPLING
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -60,6 +63,7 @@
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.launch
 import java.util.Locale
 import java.util.TimeZone
@@ -79,7 +83,7 @@
     private val batteryController: BatteryController,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val configurationController: ConfigurationController,
-    @Main private val resources: Resources,
+    @DisplaySpecific private val resources: Resources,
     private val context: Context,
     @Main private val mainExecutor: DelayableExecutor,
     @Background private val bgExecutor: Executor,
@@ -296,7 +300,7 @@
         object : KeyguardUpdateMonitorCallback() {
             override fun onKeyguardVisibilityChanged(visible: Boolean) {
                 isKeyguardVisible = visible
-                if (!featureFlags.isEnabled(DOZING_MIGRATION_1)) {
+                if (!featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) {
                     if (!isKeyguardVisible) {
                         clock?.run {
                             smallClock.animations.doze(if (isDozing) 1f else 0f)
@@ -341,9 +345,9 @@
         keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
         disposableHandle =
             parent.repeatWhenAttached {
-                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
                     listenForDozing(this)
-                    if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
+                    if (featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) {
                         listenForDozeAmountTransition(this)
                         listenForAnyStateToAodTransition(this)
                     } else {
@@ -453,8 +457,9 @@
     @VisibleForTesting
     internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
         return scope.launch {
-            keyguardTransitionInteractor.anyStateToAodTransition
-                .filter { it.transitionState == TransitionState.FINISHED }
+            keyguardTransitionInteractor.transitionStepsToState(AOD)
+                .filter { it.transitionState == TransitionState.STARTED }
+                .filter { it.from != LOCKSCREEN }
                 .collect { handleDoze(1f) }
         }
     }
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 51c0676..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);
     }
 
     /**
@@ -139,7 +121,7 @@
             case PROMPT_REASON_USER_REQUEST:
                 return R.string.kg_prompt_after_user_lockdown_password;
             case PROMPT_REASON_PREPARE_FOR_UPDATE:
-                return R.string.kg_prompt_unattended_update_password;
+                return R.string.kg_prompt_reason_timeout_password;
             case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
                 return R.string.kg_prompt_reason_timeout_password;
             case PROMPT_REASON_TRUSTAGENT_EXPIRED:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 714ba81..57151ae 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -321,7 +321,7 @@
                 resId = R.string.kg_prompt_after_user_lockdown_pattern;
                 break;
             case PROMPT_REASON_PREPARE_FOR_UPDATE:
-                resId = R.string.kg_prompt_unattended_update_pattern;
+                resId = R.string.kg_prompt_reason_timeout_pattern;
                 break;
             case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
                 resId = R.string.kg_prompt_reason_timeout_pattern;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 9d6d033..681aa70 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -123,7 +123,7 @@
             case PROMPT_REASON_USER_REQUEST:
                 return R.string.kg_prompt_after_user_lockdown_pin;
             case PROMPT_REASON_PREPARE_FOR_UPDATE:
-                return R.string.kg_prompt_unattended_update_pin;
+                return R.string.kg_prompt_reason_timeout_pin;
             case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
                 return R.string.kg_prompt_reason_timeout_pin;
             case PROMPT_REASON_TRUSTAGENT_EXPIRED:
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/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index f9cc03ee..d848602 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -18,7 +18,6 @@
 
 import static java.util.Collections.emptySet;
 
-import android.animation.LayoutTransition;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.os.Build;
@@ -79,14 +78,6 @@
         mKeyguardSlice = findViewById(R.id.keyguard_slice_view);
 
         mMediaHostContainer = findViewById(R.id.status_view_media_container);
-        if (mMediaHostContainer != null) {
-            LayoutTransition mediaLayoutTransition = new LayoutTransition();
-            ((ViewGroup) mMediaHostContainer).setLayoutTransition(mediaLayoutTransition);
-            mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
-            mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
-            mediaLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
-            mediaLayoutTransition.disableTransitionType(LayoutTransition.DISAPPEARING);
-        }
 
         updateDark();
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 67b7052..758d1fe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -23,7 +23,6 @@
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.animation.Animator;
-import android.animation.LayoutTransition;
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
 import android.content.res.Configuration;
@@ -50,14 +49,18 @@
 import com.android.keyguard.KeyguardClockSwitch.ClockSize;
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
+import com.android.systemui.animation.ViewHierarchyAnimator;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.power.shared.model.ScreenPowerState;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.TransitionState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.power.shared.model.ScreenPowerState;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -102,10 +105,11 @@
     private final Rect mClipBounds = new Rect();
     private final KeyguardInteractor mKeyguardInteractor;
     private final PowerInteractor mPowerInteractor;
+    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
 
     private Boolean mSplitShadeEnabled = false;
     private Boolean mStatusViewCentered = true;
-
+    private boolean mGoneToAodTransitionRunning = false;
     private DumpManager mDumpManager;
 
     private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
@@ -135,6 +139,7 @@
             FeatureFlags featureFlags,
             InteractionJankMonitor interactionJankMonitor,
             KeyguardInteractor keyguardInteractor,
+            KeyguardTransitionInteractor keyguardTransitionInteractor,
             DumpManager dumpManager,
             PowerInteractor powerInteractor) {
         super(keyguardStatusView);
@@ -144,12 +149,13 @@
         mConfigurationController = configurationController;
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
                 dozeParameters, screenOffAnimationController, /* animateYPos= */ true,
-                logger.getBuffer());
+                featureFlags, logger.getBuffer());
         mInteractionJankMonitor = interactionJankMonitor;
         mFeatureFlags = featureFlags;
         mDumpManager = dumpManager;
         mKeyguardInteractor = keyguardInteractor;
         mPowerInteractor = powerInteractor;
+        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
     }
 
     @Override
@@ -175,27 +181,10 @@
                             return;
                         }
 
-                        final LayoutTransition mediaLayoutTransition =
-                                ((ViewGroup) mediaHostContainer).getLayoutTransition();
-                        if (mediaLayoutTransition == null) return;
-
-                        mediaLayoutTransition.enableTransitionType(LayoutTransition.CHANGING);
+                        ViewHierarchyAnimator.Companion.animateNextUpdate(mediaHostContainer,
+                                Interpolators.STANDARD, /* duration= */ 500L,
+                                /* animateChildren= */ false);
                     });
-
-            mediaHostContainer.addOnLayoutChangeListener(
-                    (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
-                        final LayoutTransition mediaLayoutTransition =
-                                ((ViewGroup) mediaHostContainer).getLayoutTransition();
-                        if (mediaLayoutTransition == null) return;
-                        if (!mediaLayoutTransition.isTransitionTypeEnabled(
-                                LayoutTransition.CHANGING)) {
-                            return;
-                        }
-                        // Note: when this is called, the LayoutTransition is already been set up.
-                        // Disables the LayoutTransition until it's explicitly enabled again.
-                        mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGING);
-                    }
-            );
         }
 
         mDumpManager.registerDumpable(getInstanceName(), this);
@@ -216,6 +205,15 @@
                         dozeTimeTick();
                     }
                 }, context);
+
+        collectFlow(mView, mKeyguardTransitionInteractor.getGoneToAodTransition(),
+                (TransitionStep step) -> {
+                    if (step.getTransitionState() == TransitionState.RUNNING) {
+                        mGoneToAodTransitionRunning = true;
+                    } else {
+                        mGoneToAodTransitionRunning = false;
+                    }
+                }, context);
     }
 
     public KeyguardStatusView getView() {
@@ -283,7 +281,7 @@
      * Set keyguard status view alpha.
      */
     public void setAlpha(float alpha) {
-        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
+        if (!mKeyguardVisibilityHelper.isVisibilityAnimating() && !mGoneToAodTransitionRunning) {
             mView.setAlpha(alpha);
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 3f5ec7d..84e06e2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -153,7 +153,6 @@
 import com.android.settingslib.WirelessUtils;
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -177,6 +176,7 @@
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.WeatherData;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -189,6 +189,8 @@
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.settings.SecureSettings;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 import com.google.android.collect.Lists;
 
 import java.io.PrintWriter;
@@ -1248,6 +1250,7 @@
      * @deprecated This is being migrated to use modern architecture, this method is visible purely
      * for bridging the gap while the migration is active.
      */
+    @Deprecated
     private void handleFaceAuthFailed() {
         Assert.isMainThread();
         String reason =
@@ -1276,6 +1279,7 @@
      * @deprecated This is being migrated to use modern architecture, this method is visible purely
      * for bridging the gap while the migration is active.
      */
+    @Deprecated
     private void handleFaceAcquired(int acquireInfo) {
         Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -1297,6 +1301,7 @@
      * @deprecated This is being migrated to use modern architecture, this method is visible purely
      * for bridging the gap while the migration is active.
      */
+    @Deprecated
     private void handleFaceAuthenticated(int authUserId, boolean isStrongBiometric) {
         Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");
         try {
@@ -1325,6 +1330,7 @@
      * @deprecated This is being migrated to use modern architecture, this method is visible purely
      * for bridging the gap while the migration is active.
      */
+    @Deprecated
     private void handleFaceHelp(int msgId, String helpString) {
         if (mFaceAcquiredInfoIgnoreList.contains(msgId)) {
             return;
@@ -1342,6 +1348,7 @@
      * @deprecated This is being migrated to use modern architecture, this method is visible purely
      * for bridging the gap while the migration is active.
      */
+    @Deprecated
     private void handleFaceError(int msgId, final String originalErrMsg) {
         Assert.isMainThread();
         String errString = originalErrMsg;
@@ -1977,6 +1984,7 @@
                 @Override
                 public void onAuthenticationAcquired(int acquireInfo) {
                     Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationAcquired");
+                    mLogger.logFingerprintAcquired(acquireInfo);
                     handleFingerprintAcquired(acquireInfo);
                     Trace.endSection();
                 }
@@ -4430,6 +4438,7 @@
     }
 
     @SuppressLint("MissingPermission")
+    @NeverCompile
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("KeyguardUpdateMonitor state:");
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/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index c64ae01..d524e4a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -23,6 +23,8 @@
 import android.view.View;
 
 import com.android.app.animation.Interpolators;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.statusbar.StatusBarState;
@@ -53,6 +55,7 @@
     private boolean mKeyguardViewVisibilityAnimating;
     private boolean mLastOccludedState = false;
     private final AnimationProperties mAnimationProperties = new AnimationProperties();
+    private final FeatureFlags mFeatureFlags;
     private final LogBuffer mLogBuffer;
 
     public KeyguardVisibilityHelper(View view,
@@ -60,12 +63,14 @@
             DozeParameters dozeParameters,
             ScreenOffAnimationController screenOffAnimationController,
             boolean animateYPos,
+            FeatureFlags featureFlags,
             LogBuffer logBuffer) {
         mView = view;
         mKeyguardStateController = keyguardStateController;
         mDozeParameters = dozeParameters;
         mScreenOffAnimationController = screenOffAnimationController;
         mAnimateYPos = animateYPos;
+        mFeatureFlags = featureFlags;
         mLogBuffer = logBuffer;
     }
 
@@ -162,13 +167,17 @@
                         animProps,
                         true /* animate */);
             } else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) {
-                log("ScreenOff transition");
-                mKeyguardViewVisibilityAnimating = true;
+                if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+                    log("Using GoneToAodTransition");
+                    mKeyguardViewVisibilityAnimating = false;
+                } else {
+                    log("ScreenOff transition");
+                    mKeyguardViewVisibilityAnimating = true;
 
-                // Ask the screen off animation controller to animate the keyguard visibility for us
-                // since it may need to be cancelled due to keyguard lifecycle events.
-                mScreenOffAnimationController.animateInKeyguard(
-                        mView, mSetVisibleEndRunnable);
+                    // Ask the screen off animation controller to animate the keyguard visibility
+                    // for us since it may need to be cancelled due to keyguard lifecycle events.
+                    mScreenOffAnimationController.animateInKeyguard(mView, mSetVisibleEndRunnable);
+                }
             } else {
                 log("Direct set Visibility to VISIBLE");
                 mView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 0d3f726..83da80f 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -692,10 +692,10 @@
                 mVelocityTracker.addMovement(event);
                 // Compute pointer velocity in pixels per second.
                 mVelocityTracker.computeCurrentVelocity(1000);
-                float velocity = UdfpsController.computePointerSpeed(mVelocityTracker,
+                float velocity = computePointerSpeed(mVelocityTracker,
                         mActivePointerId);
                 if (event.getClassification() != MotionEvent.CLASSIFICATION_DEEP_PRESS
-                        && UdfpsController.exceedsVelocityThreshold(velocity)) {
+                        && exceedsVelocityThreshold(velocity)) {
                     Log.v(TAG, "lock icon long-press rescheduled due to "
                             + "high pointer velocity=" + velocity);
                     mLongPressCancelRunnable.run();
@@ -713,6 +713,23 @@
         return true;
     }
 
+    /**
+     * Calculate the pointer speed given a velocity tracker and the pointer id.
+     * This assumes that the velocity tracker has already been passed all relevant motion events.
+     */
+    private static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) {
+        final float vx = tracker.getXVelocity(pointerId);
+        final float vy = tracker.getYVelocity(pointerId);
+        return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0));
+    }
+
+    /**
+     * Whether the velocity exceeds the acceptable UDFPS debouncing threshold.
+     */
+    private static boolean exceedsVelocityThreshold(float velocity) {
+        return velocity > 750f;
+    }
+
     private boolean actionableDownEventStartedOnView(MotionEvent event) {
         if (!isActionable()) {
             return false;
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardDisplayModule.kt b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardDisplayModule.kt
index c581788..1f145d8 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardDisplayModule.kt
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardDisplayModule.kt
@@ -4,6 +4,7 @@
 import android.content.res.Resources
 import android.view.Display
 import com.android.systemui.dagger.qualifiers.DisplaySpecific
+import com.android.systemui.util.kotlin.getOrNull
 import dagger.BindsOptionalOf
 import dagger.Module
 import dagger.Provides
@@ -23,11 +24,12 @@
     companion object {
         @Provides
         @DisplaySpecific
-        fun getDisplayContext(context: Context, display: Optional<Display>): Context {
-            return if (display.isPresent) {
-                context.createDisplayContext(display.get())
-            } else {
+        fun getDisplayContext(context: Context, optionalDisplay: Optional<Display>): Context {
+            val display = optionalDisplay.getOrNull() ?: return context
+            return if (context.displayId == display.displayId) {
                 context
+            } else {
+                context.createDisplayContext(display)
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 2dfb370..fa07072 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -44,6 +44,7 @@
 import javax.inject.Inject
 
 private const val TAG = "KeyguardUpdateMonitorLog"
+private const val FP_LOG_TAG = "KeyguardFingerprintLog"
 
 /** Helper class for logging for [com.android.keyguard.KeyguardUpdateMonitor] */
 class KeyguardUpdateMonitorLogger
@@ -157,7 +158,7 @@
 
     fun logFingerprintAuthForWrongUser(authUserId: Int) {
         logBuffer.log(
-            TAG,
+            FP_LOG_TAG,
             DEBUG,
             { int1 = authUserId },
             { "Fingerprint authenticated for wrong user: $int1" }
@@ -166,7 +167,7 @@
 
     fun logFingerprintDisabledForUser(userId: Int) {
         logBuffer.log(
-            TAG,
+            FP_LOG_TAG,
             DEBUG,
             { int1 = userId },
             { "Fingerprint disabled by DPM for userId: $int1" }
@@ -174,12 +175,17 @@
     }
 
     fun logFingerprintLockoutReset(@LockoutMode mode: Int) {
-        logBuffer.log(TAG, DEBUG, { int1 = mode }, { "handleFingerprintLockoutReset: $int1" })
+        logBuffer.log(
+            FP_LOG_TAG,
+            DEBUG,
+            { int1 = mode },
+            { "handleFingerprintLockoutReset: $int1" }
+        )
     }
 
     fun logFingerprintRunningState(fingerprintRunningState: Int) {
         logBuffer.log(
-            TAG,
+            FP_LOG_TAG,
             DEBUG,
             { int1 = fingerprintRunningState },
             { "fingerprintRunningState: $int1" }
@@ -188,7 +194,7 @@
 
     fun logFingerprintSuccess(userId: Int, isStrongBiometric: Boolean) {
         logBuffer.log(
-            TAG,
+            FP_LOG_TAG,
             DEBUG,
             {
                 int1 = userId
@@ -212,7 +218,7 @@
 
     fun logFingerprintDetected(userId: Int, isStrongBiometric: Boolean) {
         logBuffer.log(
-            TAG,
+            FP_LOG_TAG,
             DEBUG,
             {
                 int1 = userId
@@ -224,7 +230,7 @@
 
     fun logFingerprintError(msgId: Int, originalErrMsg: String) {
         logBuffer.log(
-            TAG,
+            FP_LOG_TAG,
             DEBUG,
             {
                 str1 = originalErrMsg
@@ -451,7 +457,7 @@
     }
 
     fun logSubInfo(subInfo: SubscriptionInfo?) {
-        logBuffer.log(TAG, VERBOSE, { str1 = "$subInfo" }, { "SubInfo:$str1" })
+        logBuffer.log(TAG, DEBUG, { str1 = "$subInfo" }, { "SubInfo:$str1" })
     }
 
     fun logTimeFormatChanged(newTimeFormat: String?) {
@@ -751,4 +757,25 @@
             { "userSwitchComplete: $str1, userId: $int1" }
         )
     }
+
+    fun logFingerprintHelp(helpMsgId: Int, helpString: CharSequence) {
+        logBuffer.log(
+            FP_LOG_TAG,
+            DEBUG,
+            {
+                int1 = helpMsgId
+                str1 = "$helpString"
+            },
+            { "fingerprint help message: $int1, $str1" }
+        )
+    }
+
+    fun logFingerprintAcquired(acquireInfo: Int) {
+        logBuffer.log(
+            FP_LOG_TAG,
+            DEBUG,
+            { int1 = acquireInfo },
+            { "fingerprint acquire message: $int1" }
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 7739021..1a34cc4 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -15,123 +15,55 @@
 package com.android.systemui;
 
 import android.annotation.Nullable;
-import android.app.AlarmManager;
-import android.app.INotificationManager;
-import android.app.IWallpaperManager;
-import android.hardware.SensorPrivacyManager;
-import android.hardware.display.NightDisplayListener;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.ArrayMap;
-import android.util.DisplayMetrics;
-import android.view.IWindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.Preconditions;
-import com.android.keyguard.KeyguardSecurityModel;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
-import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
 import com.android.systemui.animation.DialogLaunchAnimator;
-import com.android.systemui.appops.AppOpsController;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dock.DockManager;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentService;
-import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
-import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.PluginDependencyProvider;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.EnhancedEstimates;
-import com.android.systemui.power.PowerUI;
-import com.android.systemui.privacy.PrivacyItemController;
-import com.android.systemui.qs.ReduceBrightColorsController;
-import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
 import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
-import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.SmartReplyController;
-import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.events.PrivacyDotViewController;
-import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
-import com.android.systemui.statusbar.phone.AutoHideController;
-import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
-import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
-import com.android.systemui.statusbar.policy.AccessibilityController;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.BluetoothController;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.DataSaverController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.FlashlightController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.LocationController;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
-import com.android.systemui.statusbar.policy.RotationLockController;
-import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.statusbar.policy.SensorPrivacyController;
-import com.android.systemui.statusbar.policy.SmartReplyConstants;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.statusbar.window.StatusBarWindowController;
-import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
 import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.leak.GarbageMonitor;
-import com.android.systemui.util.leak.LeakDetector;
-import com.android.systemui.util.leak.LeakReporter;
-import com.android.systemui.util.sensors.AsyncSensorManager;
 
 import dagger.Lazy;
 
-import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -154,10 +86,6 @@
  */
 @SysUISingleton
 public class Dependency {
-    /**
-     * Key for getting a the main looper.
-     */
-    private static final String MAIN_LOOPER_NAME = "main_looper";
 
     /**
      * Key for getting a background Looper for background work.
@@ -171,15 +99,6 @@
      * Generic handler on the main thread.
      */
     private static final String MAIN_HANDLER_NAME = "main_handler";
-    /**
-     * Generic executor on the main thread.
-     */
-    private static final String MAIN_EXECUTOR_NAME = "main_executor";
-
-    /**
-     * Generic executor on a background thread.
-     */
-    private static final String BACKGROUND_EXECUTOR_NAME = "background_executor";
 
     /**
      * An email address to send memory leak reports to by default.
@@ -197,10 +116,6 @@
      */
     public static final DependencyKey<Looper> BG_LOOPER = new DependencyKey<>(BG_LOOPER_NAME);
     /**
-     * Key for getting a mainer Looper.
-     */
-    public static final DependencyKey<Looper> MAIN_LOOPER = new DependencyKey<>(MAIN_LOOPER_NAME);
-    /**
      * Key for getting a Handler for receiving time tick broadcasts on.
      */
     public static final DependencyKey<Handler> TIME_TICK_HANDLER =
@@ -211,133 +126,43 @@
     public static final DependencyKey<Handler> MAIN_HANDLER =
             new DependencyKey<>(MAIN_HANDLER_NAME);
 
-    /**
-     * Generic executor on the main thread.
-     */
-    public static final DependencyKey<Executor> MAIN_EXECUTOR =
-            new DependencyKey<>(MAIN_EXECUTOR_NAME);
-    /**
-     * Generic executor on a background thread.
-     */
-    public static final DependencyKey<Executor> BACKGROUND_EXECUTOR =
-            new DependencyKey<>(BACKGROUND_EXECUTOR_NAME);
-
-    /**
-     * An email address to send memory leak reports to by default.
-     */
-    public static final DependencyKey<String> LEAK_REPORT_EMAIL =
-            new DependencyKey<>(LEAK_REPORT_EMAIL_NAME);
-
     private final ArrayMap<Object, Object> mDependencies = new ArrayMap<>();
     private final ArrayMap<Object, LazyDependencyCreator> mProviders = new ArrayMap<>();
 
     @Inject DumpManager mDumpManager;
 
-    @Inject Lazy<ActivityStarter> mActivityStarter;
     @Inject Lazy<BroadcastDispatcher> mBroadcastDispatcher;
-    @Inject Lazy<AsyncSensorManager> mAsyncSensorManager;
     @Inject Lazy<BluetoothController> mBluetoothController;
-    @Inject Lazy<LocationController> mLocationController;
-    @Inject Lazy<RotationLockController> mRotationLockController;
-    @Inject Lazy<ZenModeController> mZenModeController;
-    @Inject Lazy<HotspotController> mHotspotController;
-    @Inject Lazy<CastController> mCastController;
     @Inject Lazy<FlashlightController> mFlashlightController;
-    @Inject Lazy<UserSwitcherController> mUserSwitcherController;
-    @Inject Lazy<UserInfoController> mUserInfoController;
-    @Inject Lazy<KeyguardStateController> mKeyguardMonitor;
     @Inject Lazy<KeyguardUpdateMonitor> mKeyguardUpdateMonitor;
-    @Inject Lazy<NightDisplayListener> mNightDisplayListener;
-    @Inject Lazy<ReduceBrightColorsController> mReduceBrightColorsController;
-    @Inject Lazy<ManagedProfileController> mManagedProfileController;
-    @Inject Lazy<NextAlarmController> mNextAlarmController;
-    @Inject Lazy<DataSaverController> mDataSaverController;
-    @Inject Lazy<AccessibilityController> mAccessibilityController;
     @Inject Lazy<DeviceProvisionedController> mDeviceProvisionedController;
     @Inject Lazy<PluginManager> mPluginManager;
     @Inject Lazy<AssistManager> mAssistManager;
-    @Inject Lazy<SecurityController> mSecurityController;
-    @Inject Lazy<LeakDetector> mLeakDetector;
-    @Inject Lazy<LeakReporter> mLeakReporter;
-    @Inject Lazy<GarbageMonitor> mGarbageMonitor;
     @Inject Lazy<TunerService> mTunerService;
-    @Inject Lazy<NotificationShadeWindowController> mNotificationShadeWindowController;
-    @Inject Lazy<StatusBarWindowController> mTempStatusBarWindowController;
     @Inject Lazy<DarkIconDispatcher> mDarkIconDispatcher;
-    @Inject Lazy<StatusBarIconController> mStatusBarIconController;
-    @Inject Lazy<ScreenLifecycle> mScreenLifecycle;
-    @Inject Lazy<WakefulnessLifecycle> mWakefulnessLifecycle;
     @Inject Lazy<FragmentService> mFragmentService;
-    @Inject Lazy<ExtensionController> mExtensionController;
-    @Inject Lazy<PluginDependencyProvider> mPluginDependencyProvider;
     @Nullable
-    @Inject Lazy<LocalBluetoothManager> mLocalBluetoothManager;
     @Inject Lazy<VolumeDialogController> mVolumeDialogController;
     @Inject Lazy<MetricsLogger> mMetricsLogger;
-    @Inject Lazy<AccessibilityManagerWrapper> mAccessibilityManagerWrapper;
-    @Inject Lazy<SysuiColorExtractor> mSysuiColorExtractor;
     @Inject Lazy<TunablePaddingService> mTunablePaddingService;
     @Inject Lazy<UiOffloadThread> mUiOffloadThread;
-    @Inject Lazy<PowerUI.WarningsUI> mWarningsUI;
     @Inject Lazy<LightBarController> mLightBarController;
-    @Inject Lazy<IWindowManager> mIWindowManager;
     @Inject Lazy<OverviewProxyService> mOverviewProxyService;
     @Inject Lazy<NavigationModeController> mNavBarModeController;
     @Inject Lazy<AccessibilityButtonModeObserver> mAccessibilityButtonModeObserver;
     @Inject Lazy<AccessibilityButtonTargetsObserver> mAccessibilityButtonListController;
-    @Inject Lazy<EnhancedEstimates> mEnhancedEstimates;
-    @Inject Lazy<VibratorHelper> mVibratorHelper;
     @Inject Lazy<IStatusBarService> mIStatusBarService;
-    @Inject Lazy<DisplayMetrics> mDisplayMetrics;
-    @Inject Lazy<LockscreenGestureLogger> mLockscreenGestureLogger;
-    @Inject Lazy<ShadeController> mShadeController;
     @Inject Lazy<NotificationRemoteInputManager.Callback> mNotificationRemoteInputManagerCallback;
-    @Inject Lazy<AppOpsController> mAppOpsController;
     @Inject Lazy<NavigationBarController> mNavigationBarController;
-    @Inject Lazy<AccessibilityFloatingMenuController> mAccessibilityFloatingMenuController;
     @Inject Lazy<StatusBarStateController> mStatusBarStateController;
-    @Inject Lazy<NotificationLockscreenUserManager> mNotificationLockscreenUserManager;
     @Inject Lazy<NotificationMediaManager> mNotificationMediaManager;
-    @Inject Lazy<NotificationRemoteInputManager> mNotificationRemoteInputManager;
-    @Inject Lazy<SmartReplyConstants> mSmartReplyConstants;
-    @Inject Lazy<NotificationListener> mNotificationListener;
-    @Inject Lazy<NotificationLogger> mNotificationLogger;
-    @Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil;
-    @Inject Lazy<SmartReplyController> mSmartReplyController;
-    @Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler;
-    @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager;
-    @Inject Lazy<AutoHideController> mAutoHideController;
-    @Inject Lazy<PrivacyItemController> mPrivacyItemController;
     @Inject @Background Lazy<Looper> mBgLooper;
-    @Inject @Background Lazy<Handler> mBgHandler;
-    @Inject @Main Lazy<Looper> mMainLooper;
     @Inject @Main Lazy<Handler> mMainHandler;
     @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler;
-    @Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail;
-    @Inject @Main Lazy<Executor> mMainExecutor;
-    @Inject @Background Lazy<Executor> mBackgroundExecutor;
-    @Inject Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
-    @Inject Lazy<DevicePolicyManagerWrapper> mDevicePolicyManagerWrapper;
-    @Inject Lazy<PackageManagerWrapper> mPackageManagerWrapper;
-    @Inject Lazy<SensorPrivacyController> mSensorPrivacyController;
-    @Inject Lazy<DockManager> mDockManager;
-    @Inject Lazy<INotificationManager> mINotificationManager;
     @Inject Lazy<SysUiState> mSysUiStateFlagsContainer;
-    @Inject Lazy<AlarmManager> mAlarmManager;
-    @Inject Lazy<KeyguardSecurityModel> mKeyguardSecurityModel;
-    @Inject Lazy<DozeParameters> mDozeParameters;
-    @Inject Lazy<IWallpaperManager> mWallpaperManager;
     @Inject Lazy<CommandQueue> mCommandQueue;
-    @Inject Lazy<RecordingController> mRecordingController;
-    @Inject Lazy<MediaOutputDialogFactory> mMediaOutputDialogFactory;
-    @Inject Lazy<DeviceConfigProxy> mDeviceConfigProxy;
-    @Inject Lazy<TelephonyListenerManager> mTelephonyListenerManager;
-    @Inject Lazy<SystemStatusAnimationScheduler> mSystemStatusAnimationSchedulerLazy;
-    @Inject Lazy<PrivacyDotViewController> mPrivacyDotViewControllerLazy;
-    @Inject Lazy<EdgeBackGestureHandler.Factory> mEdgeBackGestureHandlerFactoryLazy;
     @Inject Lazy<UiEventLogger> mUiEventLogger;
     @Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy;
-    @Inject Lazy<InternetDialogFactory> mInternetDialogFactory;
     @Inject Lazy<FeatureFlags> mFeatureFlagsLazy;
     @Inject Lazy<NotificationSectionsManager> mNotificationSectionsManagerLazy;
     @Inject Lazy<ScreenOffAnimationController> mScreenOffAnimationController;
@@ -360,183 +185,36 @@
         // on imports.
         mProviders.put(TIME_TICK_HANDLER, mTimeTickHandler::get);
         mProviders.put(BG_LOOPER, mBgLooper::get);
-        mProviders.put(MAIN_LOOPER, mMainLooper::get);
         mProviders.put(MAIN_HANDLER, mMainHandler::get);
-        mProviders.put(MAIN_EXECUTOR, mMainExecutor::get);
-        mProviders.put(BACKGROUND_EXECUTOR, mBackgroundExecutor::get);
-        mProviders.put(ActivityStarter.class, mActivityStarter::get);
         mProviders.put(BroadcastDispatcher.class, mBroadcastDispatcher::get);
-
-        mProviders.put(AsyncSensorManager.class, mAsyncSensorManager::get);
-
         mProviders.put(BluetoothController.class, mBluetoothController::get);
-        mProviders.put(SensorPrivacyManager.class, mSensorPrivacyManager::get);
-
-        mProviders.put(LocationController.class, mLocationController::get);
-
-        mProviders.put(RotationLockController.class, mRotationLockController::get);
-
-        mProviders.put(ZenModeController.class, mZenModeController::get);
-
-        mProviders.put(HotspotController.class, mHotspotController::get);
-
-        mProviders.put(CastController.class, mCastController::get);
-
         mProviders.put(FlashlightController.class, mFlashlightController::get);
-
-        mProviders.put(KeyguardStateController.class, mKeyguardMonitor::get);
-
         mProviders.put(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor::get);
-
-        mProviders.put(UserSwitcherController.class, mUserSwitcherController::get);
-
-        mProviders.put(UserInfoController.class, mUserInfoController::get);
-
-        mProviders.put(NightDisplayListener.class, mNightDisplayListener::get);
-
-        mProviders.put(ReduceBrightColorsController.class, mReduceBrightColorsController::get);
-
-        mProviders.put(ManagedProfileController.class, mManagedProfileController::get);
-
-        mProviders.put(NextAlarmController.class, mNextAlarmController::get);
-
-        mProviders.put(DataSaverController.class, mDataSaverController::get);
-
-        mProviders.put(AccessibilityController.class, mAccessibilityController::get);
-
         mProviders.put(DeviceProvisionedController.class, mDeviceProvisionedController::get);
-
         mProviders.put(PluginManager.class, mPluginManager::get);
-
         mProviders.put(AssistManager.class, mAssistManager::get);
-
-        mProviders.put(SecurityController.class, mSecurityController::get);
-
-        mProviders.put(LeakDetector.class, mLeakDetector::get);
-
-        mProviders.put(LEAK_REPORT_EMAIL, mLeakReportEmail::get);
-
-        mProviders.put(LeakReporter.class, mLeakReporter::get);
-
-        mProviders.put(GarbageMonitor.class, mGarbageMonitor::get);
-
         mProviders.put(TunerService.class, mTunerService::get);
-
-        mProviders.put(NotificationShadeWindowController.class,
-                mNotificationShadeWindowController::get);
-
-        mProviders.put(StatusBarWindowController.class, mTempStatusBarWindowController::get);
-
         mProviders.put(DarkIconDispatcher.class, mDarkIconDispatcher::get);
-
-        mProviders.put(StatusBarIconController.class, mStatusBarIconController::get);
-
-        mProviders.put(ScreenLifecycle.class, mScreenLifecycle::get);
-
-        mProviders.put(WakefulnessLifecycle.class, mWakefulnessLifecycle::get);
-
         mProviders.put(FragmentService.class, mFragmentService::get);
-
-        mProviders.put(ExtensionController.class, mExtensionController::get);
-
-        mProviders.put(PluginDependencyProvider.class, mPluginDependencyProvider::get);
-
-        mProviders.put(LocalBluetoothManager.class, mLocalBluetoothManager::get);
-
         mProviders.put(VolumeDialogController.class, mVolumeDialogController::get);
-
         mProviders.put(MetricsLogger.class, mMetricsLogger::get);
-
-        mProviders.put(AccessibilityManagerWrapper.class, mAccessibilityManagerWrapper::get);
-
-        mProviders.put(SysuiColorExtractor.class, mSysuiColorExtractor::get);
-
         mProviders.put(TunablePaddingService.class, mTunablePaddingService::get);
-
         mProviders.put(UiOffloadThread.class, mUiOffloadThread::get);
-
-        mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get);
-
         mProviders.put(LightBarController.class, mLightBarController::get);
-
-        mProviders.put(IWindowManager.class, mIWindowManager::get);
-
         mProviders.put(OverviewProxyService.class, mOverviewProxyService::get);
-
         mProviders.put(NavigationModeController.class, mNavBarModeController::get);
-
         mProviders.put(AccessibilityButtonModeObserver.class,
                 mAccessibilityButtonModeObserver::get);
         mProviders.put(AccessibilityButtonTargetsObserver.class,
                 mAccessibilityButtonListController::get);
-
-        mProviders.put(EnhancedEstimates.class, mEnhancedEstimates::get);
-
-        mProviders.put(VibratorHelper.class, mVibratorHelper::get);
-
         mProviders.put(IStatusBarService.class, mIStatusBarService::get);
-
-        mProviders.put(DisplayMetrics.class, mDisplayMetrics::get);
-
-        mProviders.put(LockscreenGestureLogger.class, mLockscreenGestureLogger::get);
-
-        mProviders.put(ShadeController.class, mShadeController::get);
-
         mProviders.put(NotificationRemoteInputManager.Callback.class,
                 mNotificationRemoteInputManagerCallback::get);
-
-        mProviders.put(AppOpsController.class, mAppOpsController::get);
-
         mProviders.put(NavigationBarController.class, mNavigationBarController::get);
-
-        mProviders.put(AccessibilityFloatingMenuController.class,
-                mAccessibilityFloatingMenuController::get);
-
         mProviders.put(StatusBarStateController.class, mStatusBarStateController::get);
-        mProviders.put(NotificationLockscreenUserManager.class,
-                mNotificationLockscreenUserManager::get);
         mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get);
-        mProviders.put(NotificationRemoteInputManager.class,
-                mNotificationRemoteInputManager::get);
-        mProviders.put(SmartReplyConstants.class, mSmartReplyConstants::get);
-        mProviders.put(NotificationListener.class, mNotificationListener::get);
-        mProviders.put(NotificationLogger.class, mNotificationLogger::get);
-        mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get);
-        mProviders.put(SmartReplyController.class, mSmartReplyController::get);
-        mProviders.put(RemoteInputQuickSettingsDisabler.class,
-                mRemoteInputQuickSettingsDisabler::get);
-        mProviders.put(PrivacyItemController.class, mPrivacyItemController::get);
-        mProviders.put(ActivityManagerWrapper.class, mActivityManagerWrapper::get);
-        mProviders.put(DevicePolicyManagerWrapper.class, mDevicePolicyManagerWrapper::get);
-        mProviders.put(PackageManagerWrapper.class, mPackageManagerWrapper::get);
-        mProviders.put(SensorPrivacyController.class, mSensorPrivacyController::get);
-        mProviders.put(DockManager.class, mDockManager::get);
-        mProviders.put(INotificationManager.class, mINotificationManager::get);
         mProviders.put(SysUiState.class, mSysUiStateFlagsContainer::get);
-        mProviders.put(AlarmManager.class, mAlarmManager::get);
-        mProviders.put(KeyguardSecurityModel.class, mKeyguardSecurityModel::get);
-        mProviders.put(DozeParameters.class, mDozeParameters::get);
-        mProviders.put(IWallpaperManager.class, mWallpaperManager::get);
         mProviders.put(CommandQueue.class, mCommandQueue::get);
-        mProviders.put(DeviceConfigProxy.class, mDeviceConfigProxy::get);
-        mProviders.put(TelephonyListenerManager.class, mTelephonyListenerManager::get);
-
-        // TODO(b/118592525): to support multi-display , we start to add something which is
-        //                    per-display, while others may be global. I think it's time to add
-        //                    a new class maybe named DisplayDependency to solve per-display
-        //                    Dependency problem.
-        mProviders.put(AutoHideController.class, mAutoHideController::get);
-
-        mProviders.put(RecordingController.class, mRecordingController::get);
-
-        mProviders.put(MediaOutputDialogFactory.class, mMediaOutputDialogFactory::get);
-
-        mProviders.put(SystemStatusAnimationScheduler.class,
-                mSystemStatusAnimationSchedulerLazy::get);
-        mProviders.put(PrivacyDotViewController.class, mPrivacyDotViewControllerLazy::get);
-        mProviders.put(InternetDialogFactory.class, mInternetDialogFactory::get);
-        mProviders.put(EdgeBackGestureHandler.Factory.class,
-                mEdgeBackGestureHandlerFactoryLazy::get);
         mProviders.put(UiEventLogger.class, mUiEventLogger::get);
         mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get);
         mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get);
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index 4541384..b573fad 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -28,6 +28,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.QSUserSwitcherEvent;
 import com.android.systemui.settings.UserTracker;
@@ -46,6 +47,7 @@
 /**
  * Manages notification when a guest session is resumed.
  */
+@SysUISingleton
 public class GuestResumeSessionReceiver {
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 4af2c74..6faee8c 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -83,7 +83,7 @@
 import com.android.systemui.decor.RoundedCornerResDelegateImpl;
 import com.android.systemui.decor.ScreenDecorCommand;
 import com.android.systemui.log.ScreenDecorationsLogger;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
@@ -93,6 +93,8 @@
 import com.android.systemui.util.concurrency.ThreadFactory;
 import com.android.systemui.util.settings.SecureSettings;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 import kotlin.Pair;
 
 import java.io.PrintWriter;
@@ -163,7 +165,7 @@
     ScreenDecorHwcLayer mScreenDecorHwcLayer;
     private WindowManager mWindowManager;
     private int mRotation;
-    private SettingObserver mColorInversionSetting;
+    private UserSettingObserver mColorInversionSetting;
     @Nullable
     private DelayableExecutor mExecutor;
     private Handler mHandler;
@@ -684,7 +686,7 @@
 
             // Watch color inversion and invert the overlay as needed.
             if (mColorInversionSetting == null) {
-                mColorInversionSetting = new SettingObserver(mSecureSettings, mHandler,
+                mColorInversionSetting = new UserSettingObserver(mSecureSettings, mHandler,
                         Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
                         mUserTracker.getUserId()) {
                     @Override
@@ -1088,6 +1090,7 @@
         }
     }
 
+    @NeverCompile
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("ScreenDecorations state:");
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index 1ee06cc..56273eb 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -35,18 +35,19 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
 import com.android.systemui.assist.AssistLogger;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.assist.AssistantSessionEvent;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.res.R;
+
+import dagger.Lazy;
 
 import java.util.Locale;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 /**
  * Default UiController implementation. Shows white edge lights along the bottom of the phone,
  * expanding from the corners to meet in the center.
@@ -80,7 +81,8 @@
     @Inject
     public DefaultUiController(Context context, AssistLogger assistLogger,
             WindowManager windowManager, MetricsLogger metricsLogger,
-            Lazy<AssistManager> assistManagerLazy) {
+            Lazy<AssistManager> assistManagerLazy,
+            NavigationBarController navigationBarController) {
         mAssistLogger = assistLogger;
         mRoot = new FrameLayout(context);
         mWindowManager = windowManager;
@@ -103,6 +105,7 @@
 
         mInvocationLightsView = (InvocationLightsView)
                 LayoutInflater.from(context).inflate(R.layout.invocation_lights, mRoot, false);
+        mInvocationLightsView.setNavigationBarController(navigationBarController);
         mRoot.addView(mInvocationLightsView);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
index 4d89231..0cdb376 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
@@ -31,11 +31,10 @@
 import android.view.View;
 
 import com.android.settingslib.Utils;
-import com.android.systemui.Dependency;
-import com.android.systemui.res.R;
-import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBar;
+import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarTransitions;
+import com.android.systemui.res.R;
 
 import java.util.ArrayList;
 
@@ -64,6 +63,8 @@
     private final int mLightColor;
     @ColorInt
     private final int mDarkColor;
+    @Nullable
+    private NavigationBarController mNavigationBarController;
 
     // Allocate variable for screen location lookup to avoid memory alloc onDraw()
     private int[] mScreenLocation = new int[2];
@@ -279,12 +280,11 @@
 
     private void attemptRegisterNavBarListener() {
         if (!mRegistered) {
-            NavigationBarController controller = Dependency.get(NavigationBarController.class);
-            if (controller == null) {
+            if (mNavigationBarController == null) {
                 return;
             }
 
-            NavigationBar navBar = controller.getDefaultNavigationBar();
+            NavigationBar navBar = mNavigationBarController.getDefaultNavigationBar();
             if (navBar == null) {
                 return;
             }
@@ -296,12 +296,11 @@
 
     private void attemptUnregisterNavBarListener() {
         if (mRegistered) {
-            NavigationBarController controller = Dependency.get(NavigationBarController.class);
-            if (controller == null) {
+            if (mNavigationBarController == null) {
                 return;
             }
 
-            NavigationBar navBar = controller.getDefaultNavigationBar();
+            NavigationBar navBar = mNavigationBarController.getDefaultNavigationBar();
             if (navBar == null) {
                 return;
             }
@@ -310,4 +309,8 @@
             mRegistered = false;
         }
     }
+
+    public void setNavigationBarController(NavigationBarController navigationBarController) {
+        mNavigationBarController = navigationBarController;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt
deleted file mode 100644
index ca4b8ef..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-/**
- * Interface for controlling the on finger down & on finger up events.
- */
-interface AlternateUdfpsTouchProvider {
-
-    /**
-     * onPointerDown:
-     *
-     * This operation is used to notify the Fingerprint HAL that
-     * a fingerprint has been detected on the device's screen.
-     *
-     * See fingerprint/ISession#onPointerDown for more details.
-     */
-    fun onPointerDown(pointerId: Long, x: Int, y: Int, minor: Float, major: Float)
-
-    /**
-     * onPointerUp:
-     *
-     * This operation can be invoked when the HAL is performing any one of: ISession#authenticate,
-     * ISession#enroll, ISession#detectInteraction. This operation is used to indicate
-     * that a fingerprint that was previously down, is now up.
-     *
-     * See fingerprint/ISession#onPointerUp for more details.
-     */
-    fun onPointerUp(pointerId: Long)
-
-    /**
-     * onUiReady:
-     *
-     * This operation is used by the callee to notify the Fingerprint HAL that SystemUI is
-     * correctly configured for the fingerprint capture.
-     *
-     * See fingerprint/ISession#onUiReady for more details.
-     */
-    fun onUiReady()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index c1f6259..40f229b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -41,6 +41,8 @@
 import android.view.Surface
 import android.view.View
 import android.view.View.AccessibilityDelegate
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
 import android.view.ViewPropertyAnimator
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
@@ -54,13 +56,13 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.keyguard.KeyguardPINView
 import com.android.systemui.Dumpable
-import com.android.systemui.res.R
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.res.R
 import com.android.systemui.util.boundsOnScreen
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.traceSection
@@ -229,6 +231,20 @@
         }
     }
 
+    /** Hide the arrow indicator. */
+    fun hideIndicator() {
+        val lottieAnimationView =
+            overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView?
+        lottieAnimationView?.visibility = INVISIBLE
+    }
+
+    /** Show the arrow indicator. */
+    fun showIndicator() {
+        val lottieAnimationView =
+            overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView?
+        lottieAnimationView?.visibility = VISIBLE
+    }
+
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         pw.println("requests:")
         for (requestSource in requests) {
@@ -247,6 +263,10 @@
         pw.println("     displayId=${displayInfo.uniqueId}")
         pw.println("     sensorType=${sensorProps?.sensorType}")
         pw.println("     location=${sensorProps?.getLocation(displayInfo.uniqueId)}")
+        pw.println("lottieAnimationView:")
+        pw.println(
+            "     visibility=${overlayView?.findViewById<View>(R.id.sidefps_animation)?.visibility}"
+        )
 
         pw.println("overlayOffsets=$overlayOffsets")
         pw.println("isReverseDefaultRotation=$isReverseDefaultRotation")
@@ -498,5 +518,5 @@
     AUTO_SHOW,
     /** Pin, pattern or password bouncer */
     PRIMARY_BOUNCER,
-    ALTERNATE_BOUNCER
+    ALTERNATE_BOUNCER,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
index bdad413..395f68c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
@@ -37,9 +37,6 @@
     private float mDialogSuggestedAlpha = 1f;
     private float mNotificationShadeExpansion = 0f;
 
-    // Used for Udfps ellipse detection when flag is true, set by AnimationViewController
-    boolean mUseExpandedOverlay = false;
-
     // mAlpha takes into consideration the status bar expansion amount and dialog suggested alpha
     private int mAlpha;
     boolean mPauseAuth;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 79d9c1b..c9e4cbe 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -32,7 +32,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.SensorProperties;
@@ -54,7 +53,6 @@
 import android.view.HapticFeedbackConstants;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
-import android.view.VelocityTracker;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 
@@ -84,7 +82,6 @@
 import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter;
@@ -102,7 +99,6 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.Execution;
-import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.SystemClock;
 
 import kotlin.Unit;
@@ -110,7 +106,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.Executor;
 
@@ -136,13 +131,8 @@
     private static final String TAG = "UdfpsController";
     private static final long AOD_SEND_FINGER_UP_DELAY_MILLIS = 1000;
 
-    // Minimum required delay between consecutive touch logs in milliseconds.
-    private static final long MIN_TOUCH_LOG_INTERVAL = 50;
     private static final long MIN_UNCHANGED_INTERACTION_LOG_INTERVAL = 50;
 
-    // This algorithm checks whether the touch is within the sensor's bounding box.
-    private static final int BOUNDING_BOX_TOUCH_CONFIG_ID = 0;
-
     private final Context mContext;
     private final Execution mExecution;
     private final FingerprintManager mFingerprintManager;
@@ -175,8 +165,6 @@
     @Nullable private final TouchProcessor mTouchProcessor;
     @NonNull private final SessionTracker mSessionTracker;
     @NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor;
-    @NonNull private final SecureSettings mSecureSettings;
-    @NonNull private final UdfpsUtils mUdfpsUtils;
     @NonNull private final InputManager mInputManager;
     @NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
     private final boolean mIgnoreRefreshRate;
@@ -187,11 +175,8 @@
     @VisibleForTesting @NonNull UdfpsOverlayParams mOverlayParams = new UdfpsOverlayParams();
     // TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this.
     @Nullable private Runnable mAuthControllerUpdateUdfpsLocation;
-    @Nullable private final AlternateUdfpsTouchProvider mAlternateTouchProvider;
     @Nullable private UdfpsDisplayModeProvider mUdfpsDisplayMode;
 
-    // Tracks the velocity of a touch to help filter out the touches that move too fast.
-    @Nullable private VelocityTracker mVelocityTracker;
     // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active.
     private int mActivePointerId = -1;
     // Whether a pointer has been pilfered for current gesture
@@ -259,8 +244,6 @@
         final int touchConfigId = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_selected_udfps_touch_detection);
         pw.println("mSensorProps=(" + mSensorProps + ")");
-        pw.println("Using new touch detection framework: " + mFeatureFlags.isEnabled(
-                Flags.UDFPS_NEW_TOUCH_DETECTION));
         pw.println("touchConfigId: " + touchConfigId);
     }
 
@@ -272,7 +255,6 @@
             mFgExecutor.execute(() -> UdfpsController.this.showUdfpsOverlay(
                     new UdfpsControllerOverlay(
                         mContext,
-                        mFingerprintManager,
                         mInflater,
                         mWindowManager,
                         mAccessibilityManager,
@@ -286,7 +268,6 @@
                         mKeyguardStateController,
                         mUnlockedScreenOffAnimationController,
                         mUdfpsDisplayMode,
-                        mSecureSettings,
                         requestId,
                         reason,
                         callback,
@@ -299,7 +280,6 @@
                         mFeatureFlags,
                         mPrimaryBouncerInteractor,
                         mAlternateBouncerInteractor,
-                        mUdfpsUtils,
                         mUdfpsKeyguardAccessibilityDelegate,
                         mUdfpsKeyguardViewModels
                     )));
@@ -376,13 +356,9 @@
          * Debug to run onUiReady
          */
         public void debugOnUiReady(int sensorId) {
-            if (UdfpsController.this.mAlternateTouchProvider != null) {
-                UdfpsController.this.mAlternateTouchProvider.onUiReady();
-            } else {
-                final long requestId = (mOverlay != null) ? mOverlay.getRequestId() : 0L;
-                UdfpsController.this.mFingerprintManager.onUdfpsUiEvent(
-                        FingerprintManager.UDFPS_UI_READY, requestId, sensorId);
-            }
+            final long requestId = (mOverlay != null) ? mOverlay.getRequestId() : 0L;
+            UdfpsController.this.mFingerprintManager.onUdfpsUiEvent(
+                    FingerprintManager.UDFPS_UI_READY, requestId, sensorId);
         }
     }
 
@@ -423,23 +399,6 @@
         mUdfpsDisplayMode = udfpsDisplayMode;
     }
 
-    /**
-     * Calculate the pointer speed given a velocity tracker and the pointer id.
-     * This assumes that the velocity tracker has already been passed all relevant motion events.
-     */
-    public static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) {
-        final float vx = tracker.getXVelocity(pointerId);
-        final float vy = tracker.getYVelocity(pointerId);
-        return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0));
-    }
-
-    /**
-     * Whether the velocity exceeds the acceptable UDFPS debouncing threshold.
-     */
-    public static boolean exceedsVelocityThreshold(float velocity) {
-        return velocity > 750f;
-    }
-
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -457,38 +416,6 @@
         }
     };
 
-    /**
-     * Forwards touches to the udfps controller / view
-     */
-    public boolean onTouch(MotionEvent event) {
-        if (mOverlay == null || mOverlay.isHiding()) {
-            return false;
-        }
-        // TODO(b/225068271): may not be correct but no way to get the id yet
-        return onTouch(mOverlay.getRequestId(), event, false);
-    }
-
-    /**
-     * @param x                   coordinate
-     * @param y                   coordinate
-     * @param relativeToUdfpsView true if the coordinates are relative to the udfps view; else,
-     *                            calculate from the display dimensions in portrait orientation
-     */
-    private boolean isWithinSensorArea(UdfpsView udfpsView, float x, float y,
-            boolean relativeToUdfpsView) {
-        if (relativeToUdfpsView) {
-            // TODO: move isWithinSensorArea to UdfpsController.
-            return udfpsView.isWithinSensorArea(x, y);
-        }
-
-        if (mOverlay == null || mOverlay.getAnimationViewController() == null) {
-            return false;
-        }
-
-        return !mOverlay.getAnimationViewController().shouldPauseAuth()
-                && mOverlayParams.getSensorBounds().contains((int) x, (int) y);
-    }
-
     private void tryDismissingKeyguard() {
         if (!mOnFingerDown) {
             playStartHaptic();
@@ -497,15 +424,6 @@
         mAttemptedToDismissKeyguard = true;
     }
 
-    @VisibleForTesting
-    boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) {
-        if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
-            return newOnTouch(requestId, event, fromUdfpsView);
-        } else {
-            return oldOnTouch(requestId, event, fromUdfpsView);
-        }
-    }
-
     private int getBiometricSessionType() {
         if (mOverlay == null) {
             return -1;
@@ -566,7 +484,7 @@
         }
     }
 
-    private boolean newOnTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) {
+    private boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) {
         if (!fromUdfpsView) {
             Log.e(TAG, "ignoring the touch injected from outside of UdfpsView");
             return false;
@@ -580,7 +498,6 @@
                     + mOverlay.getRequestId());
             return false;
         }
-
         if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f
                 && !mAlternateBouncerInteractor.isVisibleState())
                 || mPrimaryBouncerInteractor.isInTransit()) {
@@ -654,8 +571,7 @@
                 break;
 
             case UNCHANGED:
-                if (!isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(),
-                        true) && mActivePointerId == MotionEvent.INVALID_POINTER_ID
+                if (mActivePointerId == MotionEvent.INVALID_POINTER_ID
                         && mAlternateBouncerInteractor.isVisibleState()) {
                     // No pointer on sensor, forward to keyguard if alternateBouncer is visible
                     mKeyguardViewManager.onTouch(event);
@@ -667,7 +583,7 @@
         logBiometricTouch(processedTouch.getEvent(), data);
 
         // Always pilfer pointers that are within sensor area or when alternate bouncer is showing
-        if (isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), true)
+        if (mActivePointerId != MotionEvent.INVALID_POINTER_ID
                 || mAlternateBouncerInteractor.isVisibleState()) {
             shouldPilfer = true;
         }
@@ -680,146 +596,7 @@
             mPointerPilfered = true;
         }
 
-        return processedTouch.getTouchData().isWithinBounds(mOverlayParams.getNativeSensorBounds());
-    }
-
-    private boolean oldOnTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) {
-        if (mOverlay == null) {
-            Log.w(TAG, "ignoring onTouch with null overlay");
-            return false;
-        }
-        if (!mOverlay.matchesRequestId(requestId)) {
-            Log.w(TAG, "ignoring stale touch event: " + requestId + " current: "
-                    + mOverlay.getRequestId());
-            return false;
-        }
-
-        final UdfpsView udfpsView = mOverlay.getOverlayView();
-        boolean handled = false;
-        switch (event.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-            case MotionEvent.ACTION_HOVER_ENTER:
-                Trace.beginSection("UdfpsController.onTouch.ACTION_DOWN");
-                // To simplify the lifecycle of the velocity tracker, make sure it's never null
-                // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP.
-                if (mVelocityTracker == null) {
-                    mVelocityTracker = VelocityTracker.obtain();
-                } else {
-                    // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
-                    // ACTION_DOWN, in that case we should just reuse the old instance.
-                    mVelocityTracker.clear();
-                }
-
-                final boolean withinSensorArea =
-                        isWithinSensorArea(udfpsView, event.getX(), event.getY(), fromUdfpsView);
-                if (withinSensorArea) {
-                    Trace.beginAsyncSection("UdfpsController.e2e.onPointerDown", 0);
-                    Log.v(TAG, "onTouch | action down");
-                    // The pointer that causes ACTION_DOWN is always at index 0.
-                    // We need to persist its ID to track it during ACTION_MOVE that could include
-                    // data for many other pointers because of multi-touch support.
-                    mActivePointerId = event.getPointerId(0);
-                    mVelocityTracker.addMovement(event);
-                    handled = true;
-                    mAcquiredReceived = false;
-                }
-                if ((withinSensorArea || fromUdfpsView) && shouldTryToDismissKeyguard()) {
-                    Log.v(TAG, "onTouch | dismiss keyguard ACTION_DOWN");
-                    tryDismissingKeyguard();
-                }
-
-                Trace.endSection();
-                break;
-
-            case MotionEvent.ACTION_MOVE:
-            case MotionEvent.ACTION_HOVER_MOVE:
-                Trace.beginSection("UdfpsController.onTouch.ACTION_MOVE");
-                final int idx = mActivePointerId == -1
-                        ? event.getPointerId(0)
-                        : event.findPointerIndex(mActivePointerId);
-                if (idx == event.getActionIndex()) {
-                    final boolean actionMoveWithinSensorArea =
-                            isWithinSensorArea(udfpsView, event.getX(idx), event.getY(idx),
-                                    fromUdfpsView);
-                    if ((fromUdfpsView || actionMoveWithinSensorArea)
-                            && shouldTryToDismissKeyguard()) {
-                        Log.v(TAG, "onTouch | dismiss keyguard ACTION_MOVE");
-                        tryDismissingKeyguard();
-                        break;
-                    }
-                    // Map the touch to portrait mode if the device is in landscape mode.
-                    final Point scaledTouch = mUdfpsUtils.getTouchInNativeCoordinates(
-                            idx, event, mOverlayParams);
-                    if (actionMoveWithinSensorArea) {
-                        if (mVelocityTracker == null) {
-                            // touches could be injected, so the velocity tracker may not have
-                            // been initialized (via ACTION_DOWN).
-                            mVelocityTracker = VelocityTracker.obtain();
-                        }
-                        mVelocityTracker.addMovement(event);
-                        // Compute pointer velocity in pixels per second.
-                        mVelocityTracker.computeCurrentVelocity(1000);
-                        // Compute pointer speed from X and Y velocities.
-                        final float v = computePointerSpeed(mVelocityTracker, mActivePointerId);
-                        final float minor = event.getTouchMinor(idx);
-                        final float major = event.getTouchMajor(idx);
-                        final boolean exceedsVelocityThreshold = exceedsVelocityThreshold(v);
-                        final String touchInfo = String.format(
-                                "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b",
-                                minor, major, v, exceedsVelocityThreshold);
-                        final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime;
-
-                        if (!mOnFingerDown && !mAcquiredReceived && !exceedsVelocityThreshold) {
-                            final float scale = mOverlayParams.getScaleFactor();
-                            float scaledMinor = minor / scale;
-                            float scaledMajor = major / scale;
-                            onFingerDown(requestId, scaledTouch.x, scaledTouch.y, scaledMinor,
-                                    scaledMajor);
-
-                            Log.v(TAG, "onTouch | finger down: " + touchInfo);
-                            mTouchLogTime = mSystemClock.elapsedRealtime();
-                            handled = true;
-                        } else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) {
-                            Log.v(TAG, "onTouch | finger move: " + touchInfo);
-                            mTouchLogTime = mSystemClock.elapsedRealtime();
-                        }
-                    } else {
-                        Log.v(TAG, "onTouch | finger outside");
-                        onFingerUp(requestId, udfpsView);
-                        // Maybe announce for accessibility.
-                        mFgExecutor.execute(() -> {
-                            if (mOverlay == null) {
-                                Log.e(TAG, "touch outside sensor area received"
-                                        + "but serverRequest is null");
-                                return;
-                            }
-                            mOverlay.onTouchOutsideOfSensorArea(scaledTouch);
-                        });
-                    }
-                }
-                Trace.endSection();
-                break;
-
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_HOVER_EXIT:
-                Trace.beginSection("UdfpsController.onTouch.ACTION_UP");
-                mActivePointerId = -1;
-                if (mVelocityTracker != null) {
-                    mVelocityTracker.recycle();
-                    mVelocityTracker = null;
-                }
-                Log.v(TAG, "onTouch | finger up");
-                mAttemptedToDismissKeyguard = false;
-                onFingerUp(requestId, udfpsView);
-                mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION);
-                Trace.endSection();
-                break;
-
-            default:
-                // Do nothing.
-        }
-        return handled;
+        return mActivePointerId != MotionEvent.INVALID_POINTER_ID;
     }
 
     private boolean shouldTryToDismissKeyguard() {
@@ -859,15 +636,12 @@
             @NonNull SystemUIDialogManager dialogManager,
             @NonNull LatencyTracker latencyTracker,
             @NonNull ActivityLaunchAnimator activityLaunchAnimator,
-            @NonNull Optional<Provider<AlternateUdfpsTouchProvider>> alternateTouchProvider,
             @NonNull @BiometricsBackground Executor biometricsExecutor,
             @NonNull PrimaryBouncerInteractor primaryBouncerInteractor,
             @NonNull SinglePointerTouchProcessor singlePointerTouchProcessor,
             @NonNull SessionTracker sessionTracker,
             @NonNull AlternateBouncerInteractor alternateBouncerInteractor,
-            @NonNull SecureSettings secureSettings,
             @NonNull InputManager inputManager,
-            @NonNull UdfpsUtils udfpsUtils,
             @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
             @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate,
             @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider) {
@@ -900,7 +674,6 @@
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
         mLatencyTracker = latencyTracker;
         mActivityLaunchAnimator = activityLaunchAnimator;
-        mAlternateTouchProvider = alternateTouchProvider.map(Provider::get).orElse(null);
         mSensorProps = new FingerprintSensorPropertiesInternal(
                 -1 /* sensorId */,
                 SensorProperties.STRENGTH_CONVENIENCE,
@@ -912,13 +685,10 @@
         mBiometricExecutor = biometricsExecutor;
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
         mAlternateBouncerInteractor = alternateBouncerInteractor;
-        mSecureSettings = secureSettings;
-        mUdfpsUtils = udfpsUtils;
         mInputManager = inputManager;
         mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
 
-        mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
-                ? singlePointerTouchProcessor : null;
+        mTouchProcessor = singlePointerTouchProcessor;
         mSessionTracker = sessionTracker;
 
         mDumpManager.registerDumpable(TAG, this);
@@ -1172,16 +942,9 @@
     }
 
     private void dispatchOnUiReady(long requestId) {
-        if (mAlternateTouchProvider != null) {
-            mBiometricExecutor.execute(() -> {
-                mAlternateTouchProvider.onUiReady();
-                mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
-            });
-        } else {
-            mFingerprintManager.onUdfpsUiEvent(FingerprintManager.UDFPS_UI_READY, requestId,
-                    mSensorProps.sensorId);
-            mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
-        }
+        mFingerprintManager.onUdfpsUiEvent(FingerprintManager.UDFPS_UI_READY, requestId,
+                mSensorProps.sensorId);
+        mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
     }
 
     private void onFingerDown(
@@ -1241,24 +1004,8 @@
             }
         }
         mOnFingerDown = true;
-        if (mAlternateTouchProvider != null) {
-            mBiometricExecutor.execute(() -> {
-                mAlternateTouchProvider.onPointerDown(requestId, (int) x, (int) y, minor, major);
-            });
-            mFgExecutor.execute(() -> {
-                if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
-                    mKeyguardUpdateMonitor.onUdfpsPointerDown((int) requestId);
-                }
-            });
-        } else {
-            if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
-                mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y,
-                        minor, major, orientation, time, gestureStart, isAod);
-            } else {
-                mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, (int) x,
-                        (int) y, minor, major);
-            }
-        }
+        mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y,
+                minor, major, orientation, time, gestureStart, isAod);
         Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0);
         final UdfpsView view = mOverlay.getOverlayView();
         if (view != null && isOptical()) {
@@ -1305,23 +1052,8 @@
         mActivePointerId = -1;
         mAcquiredReceived = false;
         if (mOnFingerDown) {
-            if (mAlternateTouchProvider != null) {
-                mBiometricExecutor.execute(() -> {
-                    mAlternateTouchProvider.onPointerUp(requestId);
-                });
-                mFgExecutor.execute(() -> {
-                    if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
-                        mKeyguardUpdateMonitor.onUdfpsPointerUp((int) requestId);
-                    }
-                });
-            } else {
-                if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
-                    mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId, pointerId, x,
-                            y, minor, major, orientation, time, gestureStart, isAod);
-                } else {
-                    mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId);
-                }
-            }
+            mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId, pointerId, x,
+                    y, minor, major, orientation, time, gestureStart, isAod);
             for (Callback cb : mCallbacks) {
                 cb.onFingerUp();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 34a0d8a..7130bfb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -20,7 +20,6 @@
 import android.annotation.UiThread
 import android.content.Context
 import android.graphics.PixelFormat
-import android.graphics.Point
 import android.graphics.Rect
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
@@ -29,7 +28,6 @@
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
 import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
-import android.hardware.fingerprint.FingerprintManager
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
 import android.os.Build
 import android.os.RemoteException
@@ -54,7 +52,6 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.flags.Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS
 import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
@@ -65,7 +62,6 @@
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.settings.SecureSettings
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import javax.inject.Provider
 
@@ -83,7 +79,6 @@
 @UiThread
 class UdfpsControllerOverlay @JvmOverloads constructor(
         private val context: Context,
-        fingerprintManager: FingerprintManager,
         private val inflater: LayoutInflater,
         private val windowManager: WindowManager,
         private val accessibilityManager: AccessibilityManager,
@@ -97,7 +92,6 @@
         private val keyguardStateController: KeyguardStateController,
         private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
         private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
-        private val secureSettings: SecureSettings,
         val requestId: Long,
         @ShowReason val requestReason: Int,
         private val controllerCallback: IUdfpsOverlayControllerCallback,
@@ -107,7 +101,6 @@
         private val primaryBouncerInteractor: PrimaryBouncerInteractor,
         private val alternateBouncerInteractor: AlternateBouncerInteractor,
         private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
-        private val udfpsUtils: UdfpsUtils,
         private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
         private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>,
 ) {
@@ -134,10 +127,7 @@
         privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
         // Avoid announcing window title.
         accessibilityTitle = " "
-
-        if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
-            inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
-        }
+        inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
     }
 
     /** If the overlay is currently showing. */
@@ -206,7 +196,6 @@
                         overlayTouchListener!!
                     )
                     overlayTouchListener?.onTouchExplorationStateChanged(true)
-                    useExpandedOverlay = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
                 }
             } catch (e: RuntimeException) {
                 Log.e(TAG, "showUdfpsOverlay | failed to add window", e)
@@ -331,25 +320,6 @@
         return wasShowing
     }
 
-    /**
-     * This function computes the angle of touch relative to the sensor and maps
-     * the angle to a list of help messages which are announced if accessibility is enabled.
-     *
-     */
-    fun onTouchOutsideOfSensorArea(scaledTouch: Point) {
-        val theStr =
-            udfpsUtils.onTouchOutsideOfSensorArea(
-                touchExplorationEnabled,
-                context,
-                scaledTouch.x,
-                scaledTouch.y,
-                overlayParams
-            )
-        if (theStr != null) {
-            animationViewController?.doAnnounceForAccessibility(theStr)
-        }
-    }
-
     /** Cancel this request. */
     fun cancel() {
         try {
@@ -367,10 +337,6 @@
     ): WindowManager.LayoutParams {
         val paddingX = animation?.paddingX ?: 0
         val paddingY = animation?.paddingY ?: 0
-        if (!featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) && animation != null &&
-                animation.listenForTouchesOutsideView()) {
-            flags = flags or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-        }
 
         val isEnrollment = when (requestReason) {
             REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true
@@ -379,19 +345,15 @@
 
         // Use expanded overlay unless touchExploration enabled
         var rotatedBounds =
-            if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
-                if (accessibilityManager.isTouchExplorationEnabled && isEnrollment) {
-                    Rect(overlayParams.sensorBounds)
-                } else {
-                    Rect(
-                        0,
-                        0,
-                        overlayParams.naturalDisplayWidth,
-                        overlayParams.naturalDisplayHeight
-                    )
-                }
-            } else {
+            if (accessibilityManager.isTouchExplorationEnabled && isEnrollment) {
                 Rect(overlayParams.sensorBounds)
+            } else {
+                Rect(
+                    0,
+                    0,
+                    overlayParams.naturalDisplayWidth,
+                    overlayParams.naturalDisplayHeight
+                )
             }
 
         val rot = overlayParams.rotation
@@ -399,9 +361,9 @@
             if (!shouldRotate(animation)) {
                 Log.v(
                     TAG, "Skip rotating UDFPS bounds " + Surface.rotationToString(rot) +
-                            " animation=$animation" +
-                            " isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" +
-                            " isOccluded=${keyguardStateController.isOccluded}"
+                        " animation=$animation" +
+                        " isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" +
+                        " isOccluded=${keyguardStateController.isOccluded}"
                 )
             } else {
                 Log.v(TAG, "Rotate UDFPS bounds " + Surface.rotationToString(rot))
@@ -412,14 +374,12 @@
                     rot
                 )
 
-                if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
-                    RotationUtils.rotateBounds(
-                            sensorBounds,
-                            overlayParams.naturalDisplayWidth,
-                            overlayParams.naturalDisplayHeight,
-                            rot
-                    )
-                }
+                RotationUtils.rotateBounds(
+                    sensorBounds,
+                    overlayParams.naturalDisplayWidth,
+                    overlayParams.naturalDisplayHeight,
+                    rot
+                )
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt
index 8cc15da..afe37d4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt
@@ -43,10 +43,6 @@
         return fingerprintDrawablePlaceHolder
     }
 
-    fun useExpandedOverlay(useExpandedOverlay: Boolean) {
-        mUseExpandedOverlay = useExpandedOverlay
-    }
-
     fun isVisible(): Boolean {
         return visible
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 8ce98a9..3d5be6f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -19,7 +19,6 @@
 import android.animation.ValueAnimator
 import android.content.res.Configuration
 import android.util.MathUtils
-import android.view.MotionEvent
 import android.view.View
 import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.Lifecycle
@@ -27,16 +26,15 @@
 import com.android.app.animation.Interpolators
 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.res.R
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
@@ -80,8 +78,6 @@
     ),
     UdfpsKeyguardViewControllerAdapter {
     private val uniqueIdentifier = this.toString()
-    private val useExpandedOverlay: Boolean =
-        featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
     private var showingUdfpsBouncer = false
     private var udfpsRequested = false
     private var qsExpansion = 0f
@@ -192,24 +188,6 @@
                 updateAlpha()
                 updatePauseAuth()
             }
-
-            /**
-             * Forward touches to the UdfpsController. This allows the touch to start from outside
-             * the sensor area and then slide their finger into the sensor area.
-             */
-            override fun onTouch(event: MotionEvent) {
-                // Don't forward touches if the shade has already started expanding.
-                if (transitionToFullShadeProgress != 0f) {
-                    return
-                }
-
-                // Forwarding touches not needed with expanded overlay
-                if (useExpandedOverlay) {
-                    return
-                } else {
-                    udfpsController.onTouch(event)
-                }
-            }
         }
 
     private val occludingAppBiometricUI: OccludingAppBiometricUI =
@@ -294,7 +272,6 @@
         keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI)
         lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = this
         activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
-        view.mUseExpandedOverlay = useExpandedOverlay
         view.startIconAsyncInflate {
             val animationViewInternal: View =
                 view.requireViewById(R.id.udfps_animation_view_internal)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
index 36a42f9..95e3a76 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
@@ -298,45 +298,34 @@
         pw.println("    mUdfpsRequested=" + mUdfpsRequested);
         pw.println("    mInterpolatedDarkAmount=" + mInterpolatedDarkAmount);
         pw.println("    mAnimationType=" + mAnimationType);
-        pw.println("    mUseExpandedOverlay=" + mUseExpandedOverlay);
     }
 
     private final AsyncLayoutInflater.OnInflateFinishedListener mLayoutInflaterFinishListener =
             new AsyncLayoutInflater.OnInflateFinishedListener() {
-        @Override
-        public void onInflateFinished(View view, int resid, ViewGroup parent) {
-            mFullyInflated = true;
-            mAodFp = view.findViewById(R.id.udfps_aod_fp);
-            mLockScreenFp = view.findViewById(R.id.udfps_lockscreen_fp);
-            mBgProtection = view.findViewById(R.id.udfps_keyguard_fp_bg);
+                @Override
+                public void onInflateFinished(View view, int resid, ViewGroup parent) {
+                    mFullyInflated = true;
+                    mAodFp = view.findViewById(R.id.udfps_aod_fp);
+                    mLockScreenFp = view.findViewById(R.id.udfps_lockscreen_fp);
+                    mBgProtection = view.findViewById(R.id.udfps_keyguard_fp_bg);
 
-            updatePadding();
-            updateColor();
-            updateAlpha();
+                    updatePadding();
+                    updateColor();
+                    updateAlpha();
 
-            if (mUseExpandedOverlay) {
-                final LayoutParams lp = (LayoutParams) view.getLayoutParams();
-                lp.width = mSensorBounds.width();
-                lp.height = mSensorBounds.height();
-                RectF relativeToView = getBoundsRelativeToView(new RectF(mSensorBounds));
-                lp.setMarginsRelative(
-                        (int) relativeToView.left,
-                        (int) relativeToView.top,
-                        (int) relativeToView.right,
-                        (int) relativeToView.bottom
-                );
-                parent.addView(view, lp);
-            } else {
-                parent.addView(view);
-            }
+                    final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+                    lp.width = mSensorBounds.width();
+                    lp.height = mSensorBounds.height();
+                    RectF relativeToView = getBoundsRelativeToView(new RectF(mSensorBounds));
+                    lp.setMarginsRelative((int) relativeToView.left, (int) relativeToView.top,
+                            (int) relativeToView.right, (int) relativeToView.bottom);
+                    parent.addView(view, lp);
 
-            // requires call to invalidate to update the color
-            mLockScreenFp.addValueCallback(
-                    new KeyPath("**"), LottieProperty.COLOR_FILTER,
-                    frameInfo -> new PorterDuffColorFilter(mTextColorPrimary,
-                            PorterDuff.Mode.SRC_ATOP)
-            );
-            mOnFinishInflateRunnable.run();
-        }
-    };
+                    // requires call to invalidate to update the color
+                    mLockScreenFp.addValueCallback(new KeyPath("**"), LottieProperty.COLOR_FILTER,
+                            frameInfo -> new PorterDuffColorFilter(mTextColorPrimary,
+                                    PorterDuff.Mode.SRC_ATOP));
+                    mOnFinishInflateRunnable.run();
+                }
+            };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
index 6ce6172..76bcd6e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
@@ -19,14 +19,12 @@
 import android.graphics.Canvas
 import android.graphics.Color
 import android.graphics.Paint
-import android.graphics.PointF
 import android.graphics.Rect
 import android.graphics.RectF
 import android.util.AttributeSet
 import android.util.Log
 import android.view.MotionEvent
 import android.widget.FrameLayout
-import com.android.systemui.res.R
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.doze.DozeReceiver
 
@@ -39,10 +37,6 @@
     context: Context,
     attrs: AttributeSet?
 ) : FrameLayout(context, attrs), DozeReceiver {
-
-    // Use expanded overlay when feature flag is true, set by UdfpsViewController
-    var useExpandedOverlay: Boolean = false
-
     // sensorRect may be bigger than the sensor. True sensor dimensions are defined in
     // overlayParams.sensorBounds
     var sensorRect = Rect()
@@ -53,14 +47,6 @@
         textSize = 32f
     }
 
-    private val sensorTouchAreaCoefficient: Float =
-        context.theme.obtainStyledAttributes(attrs, R.styleable.UdfpsView, 0, 0).use { a ->
-            require(a.hasValue(R.styleable.UdfpsView_sensorTouchAreaCoefficient)) {
-                "UdfpsView must contain sensorTouchAreaCoefficient"
-            }
-            a.getFloat(R.styleable.UdfpsView_sensorTouchAreaCoefficient, 0f)
-        }
-
     /** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */
     var animationViewController: UdfpsAnimationViewController<*>? = null
 
@@ -94,22 +80,8 @@
     override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
         super.onLayout(changed, left, top, right, bottom)
 
-        val paddingX = animationViewController?.paddingX ?: 0
-        val paddingY = animationViewController?.paddingY ?: 0
-
         // Updates sensor rect in relation to the overlay view
-        if (useExpandedOverlay) {
-            animationViewController?.onSensorRectUpdated(RectF(sensorRect))
-        } else {
-            sensorRect.set(
-                    paddingX,
-                    paddingY,
-                    (overlayParams.sensorBounds.width() + paddingX),
-                    (overlayParams.sensorBounds.height() + paddingY)
-            )
-
-            animationViewController?.onSensorRectUpdated(RectF(sensorRect))
-        }
+        animationViewController?.onSensorRectUpdated(RectF(sensorRect))
     }
 
     override fun onAttachedToWindow() {
@@ -131,22 +103,6 @@
         }
     }
 
-    fun isWithinSensorArea(x: Float, y: Float): Boolean {
-        // The X and Y coordinates of the sensor's center.
-        val translation = animationViewController?.touchTranslation ?: PointF(0f, 0f)
-        val cx = sensorRect.centerX() + translation.x
-        val cy = sensorRect.centerY() + translation.y
-        // Radii along the X and Y axes.
-        val rx = (sensorRect.right - sensorRect.left) / 2.0f
-        val ry = (sensorRect.bottom - sensorRect.top) / 2.0f
-
-        return x > cx - rx * sensorTouchAreaCoefficient &&
-            x < cx + rx * sensorTouchAreaCoefficient &&
-            y > cy - ry * sensorTouchAreaCoefficient &&
-            y < cy + ry * sensorTouchAreaCoefficient &&
-            !(animationViewController?.shouldPauseAuth() ?: false)
-    }
-
     fun configureDisplay(onDisplayConfigured: Runnable) {
         isDisplayConfigured = true
         animationViewController?.onDisplayConfiguring()
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/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index f6e0296..53b6879 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -15,7 +15,10 @@
 class CommunalRepositoryImpl
 @Inject
 constructor(
-    featureFlags: FeatureFlagsClassic,
+    private val featureFlags: FeatureFlagsClassic,
 ) : CommunalRepository {
-    override val isCommunalEnabled = featureFlags.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED)
+    override val isCommunalEnabled: Boolean
+        get() =
+            featureFlags.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) &&
+                featureFlags.isEnabled(Flags.COMMUNAL_HUB)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
new file mode 100644
index 0000000..9a9b0e2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.communal.data.repository
+
+import android.provider.Settings
+import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
+import android.provider.Settings.Secure.HubModeTutorialState
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository for the current state of hub mode tutorial. Valid states are defined in
+ * [HubModeTutorialState].
+ */
+interface CommunalTutorialRepository {
+    /** Emits the tutorial state stored in Settings */
+    val tutorialSettingState: StateFlow<Int>
+
+    /** Update the tutorial state */
+    suspend fun setTutorialState(@HubModeTutorialState state: Int)
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class CommunalTutorialRepositoryImpl
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    userRepository: UserRepository,
+    private val secureSettings: SecureSettings,
+    private val userTracker: UserTracker,
+    @CommunalLog logBuffer: LogBuffer,
+) : CommunalTutorialRepository {
+
+    companion object {
+        private const val TAG = "CommunalTutorialRepository"
+    }
+
+    private data class SettingsState(
+        @HubModeTutorialState val hubModeTutorialState: Int? = null,
+    )
+
+    private val logger = Logger(logBuffer, TAG)
+
+    private val settingsState: Flow<SettingsState> =
+        userRepository.selectedUserInfo
+            .flatMapLatest { observeSettings() }
+            .shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed())
+
+    /** Emits the state of tutorial state in settings */
+    override val tutorialSettingState: StateFlow<Int> =
+        settingsState
+            .map { it.hubModeTutorialState }
+            .filterNotNull()
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = HUB_MODE_TUTORIAL_NOT_STARTED
+            )
+
+    private fun observeSettings(): Flow<SettingsState> =
+        secureSettings
+            .observerFlow(
+                userId = userTracker.userId,
+                names =
+                    arrayOf(
+                        Settings.Secure.HUB_MODE_TUTORIAL_STATE,
+                    )
+            )
+            // Force an update
+            .onStart { emit(Unit) }
+            .map { readFromSettings() }
+
+    private suspend fun readFromSettings(): SettingsState =
+        withContext(backgroundDispatcher) {
+            val userId = userTracker.userId
+            val hubModeTutorialState =
+                secureSettings.getIntForUser(
+                    Settings.Secure.HUB_MODE_TUTORIAL_STATE,
+                    HUB_MODE_TUTORIAL_NOT_STARTED,
+                    userId,
+                )
+            val settingsState = SettingsState(hubModeTutorialState)
+            logger.d({ "Communal tutorial state for user $int1 in settings: $str1" }) {
+                int1 = userId
+                str1 = settingsState.hubModeTutorialState.toString()
+            }
+
+            settingsState
+        }
+
+    override suspend fun setTutorialState(state: Int): Unit =
+        withContext(backgroundDispatcher) {
+            val userId = userTracker.userId
+            if (tutorialSettingState.value == state) {
+                return@withContext
+            }
+            logger.d({ "Update communal tutorial state to $int1 for user $int2" }) {
+                int1 = state
+                int2 = userId
+            }
+            secureSettings.putIntForUser(
+                Settings.Secure.HUB_MODE_TUTORIAL_STATE,
+                state,
+                userId,
+            )
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt
new file mode 100644
index 0000000..69b0a27
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt
@@ -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 com.android.systemui.communal.data.repository
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface CommunalTutorialRepositoryModule {
+    @Binds
+    fun communalTutorialRepository(impl: CommunalTutorialRepositoryImpl): CommunalTutorialRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 9fb8da3..04bb6ae 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -28,11 +28,13 @@
 class CommunalInteractor
 @Inject
 constructor(
-    communalRepository: CommunalRepository,
+    private val communalRepository: CommunalRepository,
     widgetRepository: CommunalWidgetRepository,
 ) {
+
     /** Whether communal features are enabled. */
-    val isCommunalEnabled: Boolean = communalRepository.isCommunalEnabled
+    val isCommunalEnabled: Boolean
+        get() = communalRepository.isCommunalEnabled
 
     /** A flow of info about the widget to be displayed, or null if widget is unavailable. */
     val appWidgetInfo: Flow<CommunalAppWidgetInfo?> = widgetRepository.stopwatchAppWidgetInfo
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
new file mode 100644
index 0000000..276df4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.domain.interactor
+
+import android.provider.Settings
+import com.android.systemui.communal.data.repository.CommunalTutorialRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+
+/** Encapsulates business-logic related to communal tutorial state. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class CommunalTutorialInteractor
+@Inject
+constructor(
+    communalTutorialRepository: CommunalTutorialRepository,
+    keyguardInteractor: KeyguardInteractor,
+) {
+    /** An observable for whether the tutorial is available. */
+    val isTutorialAvailable: Flow<Boolean> =
+        combine(
+                keyguardInteractor.isKeyguardVisible,
+                communalTutorialRepository.tutorialSettingState,
+            ) { isKeyguardVisible, tutorialSettingState ->
+                isKeyguardVisible &&
+                    tutorialSettingState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
+            }
+            .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt
new file mode 100644
index 0000000..dab6819
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.communal.ui.binder
+
+import android.widget.TextView
+import androidx.core.view.isGone
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
+
+/** View binder for communal tutorial indicator shown on keyguard. */
+object CommunalTutorialIndicatorViewBinder {
+    fun bind(
+        view: TextView,
+        viewModel: CommunalTutorialIndicatorViewModel,
+    ): DisposableHandle {
+        val disposableHandle =
+            view.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    launch {
+                        viewModel.showIndicator.collect { isVisible ->
+                            updateView(
+                                view = view,
+                                isIndicatorVisible = isVisible,
+                            )
+                        }
+                    }
+                }
+            }
+
+        return disposableHandle
+    }
+
+    private fun updateView(
+        isIndicatorVisible: Boolean,
+        view: TextView,
+    ) {
+        if (!isIndicatorVisible) {
+            view.isGone = true
+            return
+        }
+
+        if (!view.isVisible) {
+            view.isVisible = true
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
index 3ff1f09..d8d1dc0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal.ui.view.layout.blueprints
 
+import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalHubSection
 import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalWidgetSection
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
@@ -28,10 +29,15 @@
 class DefaultCommunalBlueprint
 @Inject
 constructor(
+    defaultCommunalHubSection: DefaultCommunalHubSection,
     defaultCommunalWidgetSection: DefaultCommunalWidgetSection,
 ) : KeyguardBlueprint {
     override val id: String = COMMUNAL
-    override val sections: Set<KeyguardSection> = setOf(defaultCommunalWidgetSection)
+    override val sections: Set<KeyguardSection> =
+        setOf(
+            defaultCommunalHubSection,
+            defaultCommunalWidgetSection,
+        )
 
     companion object {
         const val COMMUNAL = "communal"
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt
new file mode 100644
index 0000000..027cc96
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.communal.ui.view.layout.sections
+
+import android.content.res.Resources
+import android.graphics.Typeface
+import android.graphics.Typeface.NORMAL
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.TextView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.core.content.res.ResourcesCompat
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.ui.binder.CommunalTutorialIndicatorViewBinder
+import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.view.layout.sections.removeView
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+
+class CommunalTutorialIndicatorSection
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val communalTutorialIndicatorViewModel: CommunalTutorialIndicatorViewModel,
+    private val communalInteractor: CommunalInteractor,
+) : KeyguardSection() {
+    private var communalTutorialIndicatorHandle: DisposableHandle? = null
+
+    override fun addViews(constraintLayout: ConstraintLayout) {
+        if (!communalInteractor.isCommunalEnabled) {
+            return
+        }
+        val padding =
+            constraintLayout.resources.getDimensionPixelSize(
+                R.dimen.communal_tutorial_indicator_padding
+            )
+        val view =
+            TextView(constraintLayout.context).apply {
+                id = R.id.communal_tutorial_indicator
+                visibility = View.GONE
+                background =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_bg,
+                        context.theme
+                    )
+                foreground =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_selected_border,
+                        context.theme
+                    )
+                gravity = Gravity.CENTER_VERTICAL
+                typeface = Typeface.create("google-sans", NORMAL)
+                text = constraintLayout.context.getString(R.string.communal_tutorial_indicator_text)
+                setPadding(padding, padding, padding, padding)
+            }
+        constraintLayout.addView(view)
+    }
+
+    override fun bindData(constraintLayout: ConstraintLayout) {
+        if (!communalInteractor.isCommunalEnabled) {
+            return
+        }
+        communalTutorialIndicatorHandle =
+            CommunalTutorialIndicatorViewBinder.bind(
+                constraintLayout.requireViewById(R.id.communal_tutorial_indicator),
+                communalTutorialIndicatorViewModel,
+            )
+    }
+
+    override fun applyConstraints(constraintSet: ConstraintSet) {
+        if (!communalInteractor.isCommunalEnabled) {
+            return
+        }
+        val tutorialIndicatorId = R.id.communal_tutorial_indicator
+        val width = resources.getDimensionPixelSize(R.dimen.communal_tutorial_indicator_fixed_width)
+        val horizontalOffsetMargin =
+            resources.getDimensionPixelSize(R.dimen.communal_tutorial_indicator_horizontal_offset)
+
+        constraintSet.apply {
+            constrainWidth(tutorialIndicatorId, width)
+            constrainHeight(tutorialIndicatorId, WRAP_CONTENT)
+            connect(
+                tutorialIndicatorId,
+                ConstraintSet.RIGHT,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.RIGHT,
+                horizontalOffsetMargin
+            )
+            connect(
+                tutorialIndicatorId,
+                ConstraintSet.TOP,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.TOP
+            )
+            connect(
+                tutorialIndicatorId,
+                ConstraintSet.BOTTOM,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.BOTTOM
+            )
+        }
+    }
+
+    override fun removeViews(constraintLayout: ConstraintLayout) {
+        communalTutorialIndicatorHandle?.dispose()
+        constraintLayout.removeView(R.id.communal_tutorial_indicator)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt
new file mode 100644
index 0000000..932dbfb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt
@@ -0,0 +1,57 @@
+package com.android.systemui.communal.ui.view.layout.sections
+
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.view.layout.sections.removeView
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** A keyguard section that hosts the communal hub. */
+class DefaultCommunalHubSection @Inject constructor() : KeyguardSection() {
+    private val communalHubViewId = R.id.communal_hub
+
+    override fun addViews(constraintLayout: ConstraintLayout) {
+        constraintLayout.addView(
+            ComposeFacade.createCommunalView(constraintLayout.context).apply {
+                id = communalHubViewId
+            },
+        )
+    }
+
+    override fun bindData(constraintLayout: ConstraintLayout) {}
+
+    override fun applyConstraints(constraintSet: ConstraintSet) {
+        constraintSet.apply {
+            connect(
+                communalHubViewId,
+                ConstraintSet.START,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.START,
+            )
+            connect(
+                communalHubViewId,
+                ConstraintSet.TOP,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.TOP,
+            )
+            connect(
+                communalHubViewId,
+                ConstraintSet.END,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.END,
+            )
+            connect(
+                communalHubViewId,
+                ConstraintSet.BOTTOM,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.BOTTOM,
+            )
+        }
+    }
+
+    override fun removeViews(constraintLayout: ConstraintLayout) {
+        constraintLayout.removeView(communalHubViewId)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt
new file mode 100644
index 0000000..eaf9550
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.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.communal.ui.viewmodel
+
+import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** View model for communal tutorial indicator on keyguard */
+class CommunalTutorialIndicatorViewModel
+@Inject
+constructor(
+    communalTutorialInteractor: CommunalTutorialInteractor,
+) {
+    /** An observable for whether the tutorial indicator view should be visible. */
+    val showIndicator: Flow<Boolean> = communalTutorialInteractor.isTutorialAvailable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 1a6f7e1..5c1539a 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -72,4 +72,9 @@
         windowInsets: StateFlow<WindowInsets?>,
         sceneByKey: Map<SceneKey, Scene>,
     ): View
+
+    /** Create a [View] that represents the communal hub. */
+    fun createCommunalView(
+        context: Context,
+    ): View
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt
index 8e3b510..436b8cb 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.qs.SettingObserver
+import com.android.systemui.qs.UserSettingObserver
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.settings.SecureSettings
 import javax.inject.Inject
@@ -64,7 +64,8 @@
             .flatMapLatest { userInfo ->
                 conflatedCallbackFlow {
                         val observer =
-                            object : SettingObserver(secureSettings, null, setting, userInfo.id) {
+                            object :
+                                UserSettingObserver(secureSettings, null, setting, userInfo.id) {
                                 override fun handleValueChanged(
                                     value: Int,
                                     observedChange: Boolean
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/dagger/GlobalModule.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
index ca725c0..5c38264 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
@@ -64,6 +64,7 @@
      * @deprecated Deprecdated because {@link Display#getMetrics} is deprecated.
      */
     @Provides
+    @Deprecated
     public DisplayMetrics provideDisplayMetrics(Context context) {
         DisplayMetrics displayMetrics = new DisplayMetrics();
         context.getDisplay().getMetrics(displayMetrics);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 4b6ad6d..7d4e1a1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -33,7 +33,6 @@
 import com.android.systemui.appops.dagger.AppOpsModule;
 import com.android.systemui.assist.AssistModule;
 import com.android.systemui.authentication.AuthenticationModule;
-import com.android.systemui.biometrics.AlternateUdfpsTouchProvider;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
 import com.android.systemui.biometrics.FingerprintReEnrollNotification;
 import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
@@ -56,6 +55,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.FlagsModule;
 import com.android.systemui.keyboard.KeyboardModule;
+import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule;
 import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule;
 import com.android.systemui.log.dagger.LogModule;
 import com.android.systemui.log.dagger.MonitorLog;
@@ -181,6 +181,7 @@
         FalsingModule.class,
         FlagsModule.class,
         FooterActionsModule.class,
+        KeyEventRepositoryModule.class,
         KeyboardModule.class,
         KeyguardBlueprintModule.class,
         LetterboxModule.class,
@@ -298,9 +299,6 @@
     abstract UdfpsDisplayModeProvider optionalUdfpsDisplayModeProvider();
 
     @BindsOptionalOf
-    abstract AlternateUdfpsTouchProvider optionalUdfpsTouchProvider();
-
-    @BindsOptionalOf
     abstract FingerprintInteractiveToAuthProvider optionalFingerprintInteractiveToAuthProvider();
 
     @BindsOptionalOf
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index 5b85ad0..1e29e1f 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -2,7 +2,7 @@
 
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -51,7 +51,7 @@
      * When this is `false`, an automatically-triggered face unlock shouldn't automatically dismiss
      * the lockscreen.
      */
-    fun isBypassEnabled(): Boolean
+    val isBypassEnabled: StateFlow<Boolean>
 }
 
 /** Encapsulates application state for device entry. */
@@ -68,7 +68,7 @@
 ) : DeviceEntryRepository {
 
     override val isUnlocked =
-        ConflatedCallbackFlow.conflatedCallbackFlow {
+        conflatedCallbackFlow {
                 val callback =
                     object : KeyguardStateController.Callback {
                         override fun onUnlockedChanged() {
@@ -112,7 +112,24 @@
         }
     }
 
-    override fun isBypassEnabled() = keyguardBypassController.bypassEnabled
+    override val isBypassEnabled: StateFlow<Boolean> =
+        conflatedCallbackFlow {
+                val listener =
+                    object : KeyguardBypassController.OnBypassStateChangedListener {
+                        override fun onBypassStateChanged(isEnabled: Boolean) {
+                            trySend(isEnabled)
+                        }
+                    }
+                keyguardBypassController.registerOnBypassStateChangedListener(listener)
+                awaitClose {
+                    keyguardBypassController.unregisterOnBypassStateChangedListener(listener)
+                }
+            }
+            .stateIn(
+                applicationScope,
+                SharingStarted.Eagerly,
+                initialValue = keyguardBypassController.bypassEnabled,
+            )
 
     companion object {
         private const val TAG = "DeviceEntryRepositoryImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 5612c9a..e96e318 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -106,5 +106,5 @@
      * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
      * lock screen.
      */
-    fun isBypassEnabled() = repository.isBypassEnabled()
+    val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
 }
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 6bbd40c..1c2ff4b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
@@ -31,12 +31,12 @@
 import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.os.Bundle;
-import android.os.UserHandle;
 import android.util.Log;
 
 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;
@@ -56,15 +56,15 @@
 
 /**
  * Concrete implementation of the a Flag manager that returns default values for debug builds
- *
+ * <p>
  * Flags can be set (or unset) via the following adb command:
- *
+ * <p>
  * adb shell cmd statusbar flag <id> <on|off|toggle|erase>
- *
+ * <p>
  * Alternatively, you can change flags via a broadcast intent:
- *
+ * <p>
  * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>]
- *
+ * <p>
  * To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
  */
 @SysUISingleton
@@ -81,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 =
@@ -125,6 +126,7 @@
             @Main Resources resources,
             ServerFlagReader serverFlagReader,
             @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags,
+            FeatureFlags gantryFlags,
             Restarter restarter) {
         mFlagManager = flagManager;
         mContext = context;
@@ -133,6 +135,7 @@
         mSystemProperties = systemProperties;
         mServerFlagReader = serverFlagReader;
         mAllFlags = allFlags;
+        mGantryFlags = gantryFlags;
         mRestarter = restarter;
     }
 
@@ -260,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(
@@ -319,8 +321,7 @@
             Log.w(TAG, "Failed to set flag " + name + " to " + value);
             return;
         }
-        mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), data,
-                UserHandle.USER_CURRENT);
+        mGlobalSettings.putString(mFlagManager.nameToSettingsKey(name), data);
     }
 
     <T> void eraseFlag(Flag<T> flag) {
@@ -354,8 +355,7 @@
     /** Works just like {@link #eraseFlag(String)} except that it doesn't restart SystemUI. */
     private void eraseInternal(String name) {
         // We can't actually "erase" things from settings, but we can set them to empty!
-        mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), "",
-                UserHandle.USER_CURRENT);
+        mGlobalSettings.putString(mFlagManager.nameToSettingsKey(name), "");
         Log.i(TAG, "Erase name " + name);
     }
 
@@ -537,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 f6f24e0..4764f22 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")
 
@@ -473,6 +479,9 @@
     // TODO(b/270437894): Tracking Bug
     val MEDIA_REMOTE_RESUME = unreleasedFlag("media_remote_resume")
 
+    // TODO(b/304506662): Tracking Bug
+    val MEDIA_DEVICE_NAME_FIX = unreleasedFlag("media_device_name_fix", teamfood = true)
+
     // 1000 - dock
     val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag("simulate_dock_through_charging")
 
@@ -617,7 +626,7 @@
 
     /** TODO(b/295143676): Tracking bug. When enable, captures a screenshot for each display. */
     @JvmField
-    val MULTI_DISPLAY_SCREENSHOT = unreleasedFlag("multi_display_screenshot")
+    val MULTI_DISPLAY_SCREENSHOT = unreleasedFlag("multi_display_screenshot", teamfood = true)
 
     // 1400 - columbus
     // TODO(b/254512756): Tracking Bug
@@ -667,8 +676,6 @@
     @JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
 
     // 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.)
-    // TODO(b/259264861): Tracking Bug
-    @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag("udfps_new_touch_detection")
 
     // 2300 - stylus
     @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag("track_stylus_ever_used")
@@ -786,8 +793,7 @@
 
     // TODO(b/290213663): Tracking Bug
     @JvmField
-    val ONE_WAY_HAPTICS_API_MIGRATION =
-            unreleasedFlag("oneway_haptics_api_migration", teamfood = true)
+    val ONE_WAY_HAPTICS_API_MIGRATION = releasedFlag("oneway_haptics_api_migration")
 
     /** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
new file mode 100644
index 0000000..7ccc26c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.flags
+
+import android.util.Log
+import com.android.systemui.Dependency
+
+/**
+ * This class promotes best practices for flag guarding System UI view refactors.
+ * * [isEnabled] allows changing an implementation.
+ * * [assertInLegacyMode] allows authors to flag code as being "dead" when the flag gets enabled and
+ *   ensure that it is not being invoked accidentally in the post-flag refactor.
+ * * [isUnexpectedlyInLegacyMode] allows authors to guard new code with a "safe" alternative when
+ *   invoked on flag-disabled builds, but with a check that should crash eng builds or tests when
+ *   the expectation is violated.
+ *
+ * The constructors require that you provide a [FeatureFlags] instance. If you're using this in a
+ * View class, it's acceptable to ue the [forView] constructor methods, which do not require one,
+ * falling back to [Dependency.get]. This fallback should ONLY be used to flag-guard code changes
+ * inside Views where injecting flag values after initialization can be error-prone.
+ */
+class RefactorFlag
+private constructor(
+    private val injectedFlags: FeatureFlags?,
+    private val flagName: Any,
+    private val readFlagValue: (FeatureFlags) -> Boolean
+) {
+    constructor(
+        flags: FeatureFlags,
+        flag: UnreleasedFlag
+    ) : this(flags, flag, { it.isEnabled(flag) })
+
+    constructor(flags: FeatureFlags, flag: ReleasedFlag) : this(flags, flag, { it.isEnabled(flag) })
+
+    /** Whether the flag is enabled. Called to switch between an old behavior and a new behavior. */
+    val isEnabled by lazy {
+        @Suppress("DEPRECATION")
+        val featureFlags = injectedFlags ?: Dependency.get(FeatureFlags::class.java)
+        readFlagValue(featureFlags)
+    }
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     *
+     * Example usage:
+     * ```
+     * public void setController(NotificationShelfController notificationShelfController) {
+     *     mShelfRefactor.assertInLegacyMode();
+     *     mController = notificationShelfController;
+     * }
+     * ````
+     */
+    fun assertInLegacyMode() =
+        check(!isEnabled) { "Legacy code path not supported when $flagName is enabled." }
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     *
+     * Example usage:
+     * ```
+     * public void setShelfIcons(NotificationIconContainer icons) {
+     *     if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
+     *     mShelfIcons = icons;
+     * }
+     * ```
+     */
+    fun isUnexpectedlyInLegacyMode(): Boolean {
+        if (!isEnabled) {
+            val message = "New code path expects $flagName to be enabled."
+            Log.wtf(TAG, message, Exception(message))
+        }
+        return !isEnabled
+    }
+
+    companion object {
+        private const val TAG = "RefactorFlag"
+
+        /** Construct a [RefactorFlag] within View construction where injection is impossible. */
+        @JvmStatic
+        @JvmOverloads
+        fun forView(flag: UnreleasedFlag, flags: FeatureFlags? = null) =
+            RefactorFlag(flags, flag) { it.isEnabled(flag) }
+
+        /** Construct a [RefactorFlag] within View construction where injection is impossible. */
+        @JvmStatic
+        @JvmOverloads
+        fun forView(flag: ReleasedFlag, flags: FeatureFlags? = null) =
+            RefactorFlag(flags, flag) { it.isEnabled(flag) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt
deleted file mode 100644
index eaecda5..0000000
--- a/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt
+++ /dev/null
@@ -1,99 +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.flags
-
-import android.util.Log
-import com.android.systemui.Dependency
-
-/**
- * This class promotes best practices for flag guarding System UI view refactors.
- * * [isEnabled] allows changing an implementation.
- * * [assertDisabled] allows authors to flag code as being "dead" when the flag gets enabled and
- *   ensure that it is not being invoked accidentally in the post-flag refactor.
- * * [expectEnabled] allows authors to guard new code with a "safe" alternative when invoked on
- *   flag-disabled builds, but with a check that should crash eng builds or tests when the
- *   expectation is violated.
- *
- * The constructors prefer that you provide a [FeatureFlags] instance, but does not require it,
- * falling back to [Dependency.get]. This fallback should ONLY be used to flag-guard code changes
- * inside views where injecting flag values after initialization can be error-prone.
- */
-class ViewRefactorFlag
-private constructor(
-    private val injectedFlags: FeatureFlags?,
-    private val flag: BooleanFlag,
-    private val readFlagValue: (FeatureFlags) -> Boolean
-) {
-    @JvmOverloads
-    constructor(
-        flags: FeatureFlags? = null,
-        flag: UnreleasedFlag
-    ) : this(flags, flag, { it.isEnabled(flag) })
-
-    @JvmOverloads
-    constructor(
-        flags: FeatureFlags? = null,
-        flag: ReleasedFlag
-    ) : this(flags, flag, { it.isEnabled(flag) })
-
-    /** Whether the flag is enabled. Called to switch between an old behavior and a new behavior. */
-    val isEnabled by lazy {
-        @Suppress("DEPRECATION")
-        val featureFlags = injectedFlags ?: Dependency.get(FeatureFlags::class.java)
-        readFlagValue(featureFlags)
-    }
-
-    /**
-     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
-     * the flag is enabled to ensure that the refactor author catches issues in testing.
-     *
-     * Example usage:
-     * ```
-     * public void setController(NotificationShelfController notificationShelfController) {
-     *     mShelfRefactor.assertDisabled();
-     *     mController = notificationShelfController;
-     * }
-     * ````
-     */
-    fun assertDisabled() = check(!isEnabled) { "Code path not supported when $flag is enabled." }
-
-    /**
-     * Called to ensure code is only run when the flag is enabled. This protects users from the
-     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
-     * build to ensure that the refactor author catches issues in testing.
-     *
-     * Example usage:
-     * ```
-     * public void setShelfIcons(NotificationIconContainer icons) {
-     *     if (mShelfRefactor.expectEnabled()) {
-     *         mShelfIcons = icons;
-     *     }
-     * }
-     * ```
-     */
-    fun expectEnabled(): Boolean {
-        if (!isEnabled) {
-            val message = "Code path not supported when $flag is disabled."
-            Log.wtf(TAG, message, Exception(message))
-        }
-        return isEnabled
-    }
-
-    private companion object {
-        private const val TAG = "ViewRefactorFlag"
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index ac40230..c6c1f79 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -749,7 +749,7 @@
     @VisibleForTesting
     boolean shouldDisplayBugReport(@Nullable UserInfo user) {
         return user != null && user.isAdmin()
-                && mGlobalSettings.getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 0,
+                && mSecureSettings.getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 0,
                 user.id) != 0;
     }
 
@@ -1091,7 +1091,7 @@
 
         @Override
         public boolean showBeforeProvisioning() {
-            return Build.isDebuggable() && mGlobalSettings.getIntForUser(
+            return Build.isDebuggable() && mSecureSettings.getIntForUser(
                     Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, getCurrentUser().id) != 0
                     && getCurrentUser().isAdmin();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepository.kt b/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepository.kt
new file mode 100644
index 0000000..5bc5d0b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepository.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.keyevent.data.repository
+
+import android.view.KeyEvent
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.CommandQueue
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Defines interface for classes that encapsulate application state for key event presses. */
+interface KeyEventRepository {
+    /** Observable for whether the power button key is pressed/down or not. */
+    val isPowerButtonDown: Flow<Boolean>
+}
+
+@SysUISingleton
+class KeyEventRepositoryImpl
+@Inject
+constructor(
+    val commandQueue: CommandQueue,
+) : KeyEventRepository {
+    override val isPowerButtonDown: Flow<Boolean> = conflatedCallbackFlow {
+        val callback =
+            object : CommandQueue.Callbacks {
+                override fun handleSystemKey(event: KeyEvent) {
+                    if (event.keyCode == KeyEvent.KEYCODE_POWER) {
+                        trySendWithFailureLogging(event.isDown, TAG, "updated isPowerButtonDown")
+                    }
+                }
+            }
+        commandQueue.addCallback(callback)
+        awaitClose { commandQueue.removeCallback(callback) }
+    }
+
+    companion object {
+        private const val TAG = "KeyEventRepositoryImpl"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepositoryModule.kt
new file mode 100644
index 0000000..afba5db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepositoryModule.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.keyevent.data.repository
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface KeyEventRepositoryModule {
+    @Binds fun keyEventRepository(impl: KeyEventRepositoryImpl): KeyEventRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt
index 3f2f67d..9949fa5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt
@@ -13,55 +13,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.systemui.keyevent.domain.interactor
 
-import android.view.KeyEvent
-import com.android.systemui.back.domain.interactor.BackActionInteractor
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor
+import com.android.systemui.keyevent.data.repository.KeyEventRepository
 import javax.inject.Inject
 
 /**
- * Sends key events to the appropriate interactors and then acts upon key events that haven't
- * already been handled but should be handled by SystemUI.
+ * Business logic for all key event state. This includes all key events, regardless of whether
+ * they've been handled or not by a consumer.
+ *
+ * For key events that SysUI wants to properly handle, see [SysUIKeyEventHandler].
  */
 @SysUISingleton
 class KeyEventInteractor
 @Inject
 constructor(
-    private val backActionInteractor: BackActionInteractor,
-    private val keyguardKeyEventInteractor: KeyguardKeyEventInteractor,
+    repository: KeyEventRepository,
 ) {
-    fun dispatchKeyEvent(event: KeyEvent): Boolean {
-        if (keyguardKeyEventInteractor.dispatchKeyEvent(event)) {
-            return true
-        }
-
-        when (event.keyCode) {
-            KeyEvent.KEYCODE_BACK -> {
-                if (event.handleAction()) {
-                    backActionInteractor.onBackRequested()
-                }
-                return true
-            }
-        }
-        return false
-    }
-
-    fun interceptMediaKey(event: KeyEvent): Boolean {
-        return keyguardKeyEventInteractor.interceptMediaKey(event)
-    }
-
-    fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
-        return keyguardKeyEventInteractor.dispatchKeyEventPreIme(event)
-    }
-
-    companion object {
-        // Most actions shouldn't be handled on the down event and instead handled on subsequent
-        // key events like ACTION_UP.
-        fun KeyEvent.handleAction(): Boolean {
-            return action != KeyEvent.ACTION_DOWN
-        }
-    }
+    val isPowerButtonDown = repository.isPowerButtonDown
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandler.kt b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandler.kt
new file mode 100644
index 0000000..1febc79
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandler.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.keyevent.domain.interactor
+
+import android.view.KeyEvent
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor
+import javax.inject.Inject
+
+/**
+ * Sends key events to the appropriate interactors and then acts upon key events that haven't
+ * already been handled but should be handled by SystemUI.
+ *
+ * To observe any key event states, see [KeyEventInteractor].
+ */
+@SysUISingleton
+class SysUIKeyEventHandler
+@Inject
+constructor(
+    private val backActionInteractor: BackActionInteractor,
+    private val keyguardKeyEventInteractor: KeyguardKeyEventInteractor,
+) {
+    fun dispatchKeyEvent(event: KeyEvent): Boolean {
+        if (keyguardKeyEventInteractor.dispatchKeyEvent(event)) {
+            return true
+        }
+
+        when (event.keyCode) {
+            KeyEvent.KEYCODE_BACK -> {
+                if (event.handleAction()) {
+                    backActionInteractor.onBackRequested()
+                }
+                return true
+            }
+        }
+        return false
+    }
+
+    fun interceptMediaKey(event: KeyEvent): Boolean {
+        return keyguardKeyEventInteractor.interceptMediaKey(event)
+    }
+
+    fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
+        return keyguardKeyEventInteractor.dispatchKeyEventPreIme(event)
+    }
+
+    companion object {
+        // Most actions shouldn't be handled on the down event and instead handled on subsequent
+        // key events like ACTION_UP.
+        fun KeyEvent.handleAction(): Boolean {
+            return action != KeyEvent.ACTION_DOWN
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index a1f0e77..b45613e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -269,6 +269,11 @@
                 }
             }
 
+            @Override
+            public void onTransitionConsumed(IBinder transition, boolean aborted)
+                    throws RemoteException {
+            }
+
             private static void initAlphaForAnimationTargets(@NonNull SurfaceControl.Transaction t,
                     @NonNull RemoteAnimationTarget[] targets) {
                 for (RemoteAnimationTarget target : targets) {
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/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 86bf368..a511713 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.view.LayoutInflater
 import android.view.View
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.keyguard.KeyguardStatusView
 import com.android.keyguard.KeyguardStatusViewController
 import com.android.keyguard.LockIconView
@@ -71,6 +72,7 @@
     private val keyguardIndicationController: KeyguardIndicationController,
     private val lockIconViewController: LockIconViewController,
     private val shadeInteractor: ShadeInteractor,
+    private val interactionJankMonitor: InteractionJankMonitor
 ) : CoreStartable {
 
     private var rootViewHandle: DisposableHandle? = null
@@ -140,6 +142,7 @@
                 keyguardStateController,
                 shadeInteractor,
                 { keyguardStatusViewController!!.getClockController() },
+                interactionJankMonitor,
             )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 41bde91..081edd1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -39,6 +39,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.classifier.FalsingModule;
 import com.android.systemui.communal.data.repository.CommunalRepositoryModule;
+import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule;
 import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -96,6 +97,7 @@
         KeyguardUserSwitcherComponent.class},
         includes = {
             CommunalRepositoryModule.class,
+            CommunalTutorialRepositoryModule.class,
             CommunalWidgetRepositoryModule.class,
             FalsingModule.class,
             KeyguardDataQuickAffordanceModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 2f80106..5659623 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageAuditLogger
+import com.android.systemui.keyguard.ui.binder.SideFpsProgressBarViewBinder
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -32,6 +33,11 @@
     @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
 
     @Binds
+    @IntoMap
+    @ClassKey(SideFpsProgressBarViewBinder::class)
+    fun bindSideFpsProgressBarViewBinder(viewBinder: SideFpsProgressBarViewBinder): CoreStartable
+
+    @Binds
     fun keyguardSurfaceBehindRepository(
         impl: KeyguardSurfaceBehindRepositoryImpl
     ): KeyguardSurfaceBehindRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 38eb730..6e0aa4c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -26,10 +26,11 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
-import javax.inject.Inject
 
 @SysUISingleton
 class FromAodTransitionInteractor
@@ -86,15 +87,19 @@
                 }
         }
     }
-
     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
             interpolator = Interpolators.LINEAR
-            duration = TRANSITION_DURATION_MS
+            duration =
+                when (toState) {
+                    KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+                    else -> DEFAULT_DURATION
+                }.inWholeMilliseconds
         }
     }
 
     companion object {
-        private const val TRANSITION_DURATION_MS = 500L
+        val TO_LOCKSCREEN_DURATION = 500.milliseconds
+        private val DEFAULT_DURATION = 500.milliseconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 75aa4b60f..ca882e5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -29,9 +29,7 @@
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -64,29 +62,14 @@
 
     private fun listenForDreamingToOccluded() {
         scope.launch {
-            keyguardInteractor.isDreaming
-                // Add a slight delay, as dreaming and occluded events will arrive with a small gap
-                // in time. This prevents a transition to OCCLUSION happening prematurely.
-                .onEach { delay(50) }
-                .sample(
-                    combine(
-                        keyguardInteractor.isKeyguardOccluded,
-                        transitionInteractor.startedKeyguardTransitionStep,
-                        ::Pair,
-                    ),
-                    ::toTriple
-                )
-                .collect { (isDreaming, isOccluded, lastStartedTransition) ->
+            combine(keyguardInteractor.isKeyguardOccluded, keyguardInteractor.isDreaming, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::toTriple)
+                .collect { (isOccluded, isDreaming, lastStartedTransition) ->
                     if (
                         isOccluded &&
                             !isDreaming &&
-                            (lastStartedTransition.to == KeyguardState.DREAMING ||
-                                lastStartedTransition.to == KeyguardState.LOCKSCREEN)
+                            lastStartedTransition.to == KeyguardState.DREAMING
                     ) {
-                        // At the moment, checking for LOCKSCREEN state above provides a corrective
-                        // action. There's no great signal to determine when the dream is ending
-                        // and a transition to OCCLUDED is beginning directly. For now, the solution
-                        // is DREAMING->LOCKSCREEN->OCCLUDED
                         startTransitionTo(KeyguardState.OCCLUDED)
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index ad51e74..c67153a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -114,7 +114,8 @@
                 .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
                     if (lastStartedStep.to == KeyguardState.GONE && isAsleep) {
                         startTransitionTo(
-                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
+                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING,
+                            resetIfCancelled = true,
                         )
                     }
                 }
@@ -127,6 +128,7 @@
             duration =
                 when (toState) {
                     KeyguardState.DREAMING -> TO_DREAMING_DURATION
+                    KeyguardState.AOD -> TO_AOD_DURATION
                     else -> DEFAULT_DURATION
                 }.inWholeMilliseconds
         }
@@ -134,5 +136,6 @@
     companion object {
         private val DEFAULT_DURATION = 500.milliseconds
         val TO_DREAMING_DURATION = 933.milliseconds
+        val TO_AOD_DURATION = 1100.milliseconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index ffa1a49..c39a4c9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -33,6 +33,9 @@
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -40,9 +43,6 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
-import java.util.UUID
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
 
 @SysUISingleton
 class FromLockscreenTransitionInteractor
@@ -318,16 +318,9 @@
     private fun listenForLockscreenToOccluded() {
         scope.launch {
             keyguardInteractor.isKeyguardOccluded
-                .sample(
-                    combine(
-                        transitionInteractor.startedKeyguardState,
-                        keyguardInteractor.isDreaming,
-                        ::Pair
-                    ),
-                    ::toTriple
-                )
-                .collect { (isOccluded, keyguardState, isDreaming) ->
-                    if (isOccluded && !isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
+                .sample(transitionInteractor.startedKeyguardState, ::Pair)
+                .collect { (isOccluded, keyguardState) ->
+                    if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) {
                         startTransitionTo(KeyguardState.OCCLUDED)
                     }
                 }
@@ -362,6 +355,7 @@
                 when (toState) {
                     KeyguardState.DREAMING -> TO_DREAMING_DURATION
                     KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
+                    KeyguardState.AOD -> TO_AOD_DURATION
                     else -> DEFAULT_DURATION
                 }.inWholeMilliseconds
         }
@@ -371,5 +365,6 @@
         private val DEFAULT_DURATION = 400.milliseconds
         val TO_DREAMING_DURATION = 933.milliseconds
         val TO_OCCLUDED_DURATION = 450.milliseconds
+        val TO_AOD_DURATION = 500.milliseconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 6e19fdb..b953b48 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -39,7 +39,6 @@
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
-import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
@@ -49,6 +48,8 @@
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import javax.inject.Provider
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.delay
@@ -64,8 +65,6 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
-import javax.inject.Inject
-import javax.inject.Provider
 
 /**
  * Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -148,9 +147,7 @@
             .combine(dozeTransitionModel) { isDreaming, dozeTransitionModel ->
                 isDreaming && isDozeOff(dozeTransitionModel.to)
             }
-            .sample(powerInteractor.isAwake) { isAbleToDream, isAwake ->
-                isAbleToDream && isAwake
-            }
+            .sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> isAbleToDream && isAwake }
             .flatMapLatest { isAbleToDream ->
                 flow {
                     delay(50)
@@ -217,11 +214,8 @@
     val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
 
     /** Notifies when a new configuration is set */
-    val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange
-
-    /** Represents the current state of the KeyguardRootView visibility */
-    val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
-        repository.keyguardRootViewVisibility
+    val configurationChange: Flow<Unit> =
+        configurationRepository.onAnyConfigurationChange.onStart { emit(Unit) }
 
     /** The position of the keyguard clock. */
     val clockPosition: Flow<Position> = repository.clockPosition
@@ -235,12 +229,17 @@
                     R.dimen.keyguard_translate_distance_on_swipe_up
                 )
             shadeRepository.shadeModel.map {
-                // On swipe up, translate the keyguard to reveal the bouncer
-                MathUtils.lerp(
-                    translationDistance,
-                    0,
-                    Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it.expansionAmount)
-                )
+                if (it.expansionAmount == 0f) {
+                    // Reset the translation value
+                    0f
+                } else {
+                    // On swipe up, translate the keyguard to reveal the bouncer
+                    MathUtils.lerp(
+                        translationDistance,
+                        0,
+                        Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it.expansionAmount)
+                    )
+                }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
index d6987629..122c4c4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
@@ -21,7 +21,7 @@
 import android.view.KeyEvent
 import com.android.systemui.back.domain.interactor.BackActionInteractor
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor.Companion.handleAction
+import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler.Companion.handleAction
 import com.android.systemui.media.controls.util.MediaSessionLegacyHelperWrapper
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -37,7 +37,6 @@
 constructor(
     private val context: Context,
     private val statusBarStateController: StatusBarStateController,
-    private val keyguardInteractor: KeyguardInteractor,
     private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
     private val shadeController: ShadeController,
     private val mediaSessionLegacyHelperWrapper: MediaSessionLegacyHelperWrapper,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
index ae18681..3c143fe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.keyguard.shared.model
 
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
 import android.hardware.fingerprint.FingerprintManager
 import android.os.SystemClock.elapsedRealtime
 
@@ -39,7 +41,12 @@
 
 /** Fingerprint acquired message. */
 data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) :
-    FingerprintAuthenticationStatus()
+    FingerprintAuthenticationStatus() {
+
+    val fingerprintCaptureStarted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_START
+
+    val fingerprintCaptureCompleted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_GOOD
+}
 
 /** Fingerprint authentication failed message. */
 object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index abc30ef..c5a8375 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -47,6 +47,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.doOnEnd
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -402,6 +403,9 @@
                         KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds
                     shakeAnimator.interpolator =
                         CycleInterpolator(KeyguardBottomAreaVibrations.ShakeAnimationCycles)
+                    shakeAnimator.doOnEnd {
+                        view.translationX = 0f
+                    }
                     shakeAnimator.start()
 
                     vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index f0d118c..99025ace 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -41,6 +41,7 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.doOnEnd
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
@@ -240,6 +241,9 @@
                         KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds
                     shakeAnimator.interpolator =
                         CycleInterpolator(KeyguardBottomAreaVibrations.ShakeAnimationCycles)
+                    shakeAnimator.doOnEnd {
+                        view.translationX = 0f
+                    }
                     shakeAnimator.start()
 
                     vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index ac4ad39..c72e6ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -20,21 +20,24 @@
 import android.view.View
 import android.view.View.OnLayoutChangeListener
 import android.view.ViewGroup
+import android.view.ViewGroup.OnHierarchyChangeListener
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.animation.Interpolators
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
+import com.android.keyguard.KeyguardClockSwitch.MISSING_CLOCK_ID
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.shared.model.TintedIcon
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.temporarydisplay.ViewPriority
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
@@ -48,8 +51,6 @@
 @ExperimentalCoroutinesApi
 object KeyguardRootViewBinder {
 
-    private var onLayoutChangeListener: OnLayoutChange? = null
-
     @JvmStatic
     fun bind(
         view: ViewGroup,
@@ -60,7 +61,14 @@
         keyguardStateController: KeyguardStateController,
         shadeInteractor: ShadeInteractor,
         clockControllerProvider: Provider<ClockController>?,
+        interactionJankMonitor: InteractionJankMonitor?,
     ): DisposableHandle {
+        var onLayoutChangeListener: OnLayoutChange? = null
+        val childViews = mutableMapOf<Int, View?>()
+        val statusViewId = R.id.keyguard_status_view
+        val burnInLayerId = R.id.burn_in_layer
+        val aodNotificationIconContainerId = R.id.aod_notification_icon_container
+        val largeClockId = R.id.lockscreen_clock_view_large
         val disposableHandle =
             view.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -86,36 +94,74 @@
 
                     if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
                         launch {
-                            viewModel.translationY.collect { y ->
-                                val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer)
-                                burnInLayer.translationY = y
+                            viewModel.burnInLayerVisibility.collect { visibility ->
+                                childViews[burnInLayerId]?.visibility = visibility
+                                // Reset alpha only for the icons, as they currently have their
+                                // own animator
+                                childViews[aodNotificationIconContainerId]?.alpha = 0f
                             }
                         }
-                    }
 
-                    if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+                        launch {
+                            viewModel.burnInLayerAlpha.collect { alpha ->
+                                childViews[statusViewId]?.alpha = alpha
+                                childViews[aodNotificationIconContainerId]?.alpha = alpha
+                            }
+                        }
+
+                        launch {
+                            viewModel.lockscreenStateAlpha.collect { alpha ->
+                                childViews[statusViewId]?.alpha = alpha
+                            }
+                        }
+
+                        launch {
+                            viewModel.translationY.collect { y ->
+                                childViews[burnInLayerId]?.translationY = y
+                            }
+                        }
+
                         launch {
                             viewModel.translationX.collect { x ->
-                                val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer)
-                                burnInLayer.translationX = x
+                                childViews[burnInLayerId]?.translationX = x
                             }
                         }
-                    }
 
-                    if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
                         launch {
                             viewModel.scale.collect { (scale, scaleClockOnly) ->
                                 if (scaleClockOnly) {
-                                    val largeClock =
-                                        view.findViewById<View?>(R.id.lockscreen_clock_view_large)
-                                    largeClock?.let {
+                                    childViews[largeClockId]?.let {
                                         it.scaleX = scale
                                         it.scaleY = scale
                                     }
                                 } else {
-                                    val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer)
-                                    burnInLayer.scaleX = scale
-                                    burnInLayer.scaleY = scale
+                                    childViews[burnInLayerId]?.scaleX = scale
+                                    childViews[burnInLayerId]?.scaleY = scale
+                                }
+                            }
+                        }
+
+                        interactionJankMonitor?.let { jankMonitor ->
+                            launch {
+                                viewModel.goneToAodTransition.collect {
+                                    when (it.transitionState) {
+                                        TransitionState.STARTED -> {
+                                            val clockId =
+                                                clockControllerProvider?.get()?.config?.id
+                                                    ?: MISSING_CLOCK_ID
+                                            val builder =
+                                                InteractionJankMonitor.Configuration.Builder
+                                                    .withView(CUJ_SCREEN_OFF_SHOW_AOD, view)
+                                                    .setTag(clockId)
+
+                                            jankMonitor.begin(builder)
+                                        }
+                                        TransitionState.CANCELED ->
+                                            jankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
+                                        TransitionState.FINISHED ->
+                                            jankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
+                                        TransitionState.RUNNING -> Unit
+                                    }
                                 }
                             }
                         }
@@ -132,54 +178,32 @@
                         }
                     }
                 }
-
-                repeatOnLifecycle(Lifecycle.State.STARTED) {
-                    if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
-                        launch {
-                            viewModel.keyguardRootViewVisibilityState.collect { visibilityState ->
-                                view.animate().cancel()
-                                val goingToFullShade = visibilityState.goingToFullShade
-                                val statusBarState = visibilityState.statusBarState
-                                val isOcclusionTransitionRunning =
-                                    visibilityState.occlusionTransitionRunning
-                                if (goingToFullShade) {
-                                    view
-                                        .animate()
-                                        .alpha(0f)
-                                        .setStartDelay(
-                                            keyguardStateController.keyguardFadingAwayDelay
-                                        )
-                                        .setDuration(
-                                            keyguardStateController.shortenedFadingAwayDuration
-                                        )
-                                        .setInterpolator(Interpolators.ALPHA_OUT)
-                                        .withEndAction { view.visibility = View.GONE }
-                                        .start()
-                                } else if (
-                                    statusBarState == StatusBarState.KEYGUARD ||
-                                        statusBarState == StatusBarState.SHADE_LOCKED
-                                ) {
-                                    view.visibility = View.VISIBLE
-                                    if (!isOcclusionTransitionRunning) {
-                                        view.alpha = 1f
-                                    }
-                                } else {
-                                    view.visibility = View.GONE
-                                }
-                            }
-                        }
-                    }
-                }
             }
         viewModel.clockControllerProvider = clockControllerProvider
 
         onLayoutChangeListener = OnLayoutChange(viewModel)
         view.addOnLayoutChangeListener(onLayoutChangeListener)
 
+        // Views will be added or removed after the call to bind(). This is needed to avoid many
+        // calls to findViewById
+        view.setOnHierarchyChangeListener(
+            object : OnHierarchyChangeListener {
+                override fun onChildViewAdded(parent: View, child: View) {
+                    childViews.put(child.id, view)
+                }
+
+                override fun onChildViewRemoved(parent: View, child: View) {
+                    childViews.remove(child.id)
+                }
+            }
+        )
+
         return object : DisposableHandle {
             override fun dispose() {
                 disposableHandle.dispose()
                 view.removeOnLayoutChangeListener(onLayoutChangeListener)
+                view.setOnHierarchyChangeListener(null)
+                childViews.clear()
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt
index f3586ba..2feaa2e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt
@@ -24,6 +24,7 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.launch
 
 /**
@@ -46,8 +47,8 @@
             constraintLayout: ConstraintLayout,
             viewModel: KeyguardBlueprintViewModel,
             finishedAddViewCallback: () -> Unit
-        ) {
-            constraintLayout.repeatWhenAttached {
+        ): DisposableHandle {
+            return constraintLayout.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.CREATED) {
                     launch {
                         viewModel.blueprint.collect { blueprint ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
new file mode 100644
index 0000000..1acea5c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.biometrics.SideFpsController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.ui.view.SideFpsProgressBar
+import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
+import com.android.systemui.util.kotlin.Quint
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class SideFpsProgressBarViewBinder
+@Inject
+constructor(
+    private val viewModel: SideFpsProgressBarViewModel,
+    private val view: SideFpsProgressBar,
+    @Application private val applicationScope: CoroutineScope,
+    private val sfpsController: dagger.Lazy<SideFpsController>,
+) : CoreStartable {
+
+    override fun start() {
+        applicationScope.launch {
+            viewModel.isProlongedTouchRequiredForAuthentication.collectLatest { enabled ->
+                if (enabled) {
+                    launch {
+                        combine(
+                                viewModel.isVisible,
+                                viewModel.sensorLocation,
+                                viewModel.shouldRotate90Degrees,
+                                viewModel.isFingerprintAuthRunning,
+                                viewModel.sensorWidth,
+                                ::Quint
+                            )
+                            .collectLatest {
+                                (visible, location, shouldRotate, fpDetectRunning, sensorWidth) ->
+                                view.updateView(visible, location, shouldRotate, sensorWidth)
+                                // We have to hide the SFPS indicator as the progress bar will
+                                // be shown at the same location
+                                if (visible) {
+                                    sfpsController.get().hideIndicator()
+                                } else if (fpDetectRunning) {
+                                    sfpsController.get().showIndicator()
+                                }
+                            }
+                    }
+                    launch { viewModel.progress.collectLatest { view.setProgress(it) } }
+                } else {
+                    view.hideOverlay()
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
index a0e0da4..475d26f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
@@ -23,7 +23,6 @@
 import androidx.asynclayoutinflater.view.AsyncLayoutInflater
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.res.R
 import com.android.systemui.biometrics.UdfpsKeyguardView
 import com.android.systemui.biometrics.ui.binder.UdfpsKeyguardInternalViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
@@ -32,6 +31,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
@@ -52,8 +52,6 @@
         fingerprintViewModel: FingerprintViewModel,
         backgroundViewModel: BackgroundViewModel,
     ) {
-        view.useExpandedOverlay(viewModel.useExpandedOverlay())
-
         val layoutInflaterFinishListener =
             AsyncLayoutInflater.OnInflateFinishedListener { inflatedInternalView, _, parent ->
                 UdfpsKeyguardInternalViewBinder.bind(
@@ -63,25 +61,21 @@
                     fingerprintViewModel,
                     backgroundViewModel,
                 )
-                if (viewModel.useExpandedOverlay()) {
-                    val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams
-                    lp.width = viewModel.sensorBounds.width()
-                    lp.height = viewModel.sensorBounds.height()
-                    val relativeToView =
-                        getBoundsRelativeToView(
-                            inflatedInternalView,
-                            RectF(viewModel.sensorBounds),
-                        )
-                    lp.setMarginsRelative(
-                        relativeToView.left.toInt(),
-                        relativeToView.top.toInt(),
-                        relativeToView.right.toInt(),
-                        relativeToView.bottom.toInt(),
+                val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams
+                lp.width = viewModel.sensorBounds.width()
+                lp.height = viewModel.sensorBounds.height()
+                val relativeToView =
+                    getBoundsRelativeToView(
+                        inflatedInternalView,
+                        RectF(viewModel.sensorBounds),
                     )
-                    parent!!.addView(inflatedInternalView, lp)
-                } else {
-                    parent!!.addView(inflatedInternalView)
-                }
+                lp.setMarginsRelative(
+                    relativeToView.left.toInt(),
+                    relativeToView.top.toInt(),
+                    relativeToView.right.toInt(),
+                    relativeToView.bottom.toInt(),
+                )
+                parent!!.addView(inflatedInternalView, lp)
             }
         val inflater = AsyncLayoutInflater(view.context)
         inflater.inflate(R.layout.udfps_keyguard_view_internal, view, layoutInflaterFinishListener)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index c6d8ec7..c1c29c4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -29,12 +29,12 @@
 import android.os.IBinder
 import android.view.Display
 import android.view.Display.DEFAULT_DISPLAY
+import android.view.DisplayInfo
 import android.view.LayoutInflater
 import android.view.SurfaceControlViewHost
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.TYPE_KEYGUARD
 import android.widget.FrameLayout
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.view.isInvisible
@@ -129,7 +129,7 @@
         bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false)
     private val wallpaperColors: WallpaperColors? = bundle.getParcelable(KEY_COLORS)
     private val displayId = bundle.getInt(KEY_DISPLAY_ID, DEFAULT_DISPLAY)
-    private val display: Display = displayManager.getDisplay(displayId)
+    private val display: Display? = displayManager.getDisplay(displayId)
 
     private var host: SurfaceControlViewHost
 
@@ -179,7 +179,7 @@
 
     fun render() {
         mainHandler.post {
-            val previewContext = context.createDisplayContext(display)
+            val previewContext = display?.let { context.createDisplayContext(it) } ?: context
 
             val rootView = FrameLayout(previewContext)
 
@@ -189,16 +189,18 @@
                 setUpBottomArea(rootView)
             }
 
-            val windowContext = context.createWindowContext(display, TYPE_KEYGUARD, null)
-            val windowManagerOfDisplay = windowContext.getSystemService(WindowManager::class.java)
+            var displayInfo: DisplayInfo? = null
+            display?.let {
+                displayInfo = DisplayInfo()
+                it.getDisplayInfo(displayInfo)
+            }
             rootView.measure(
                 View.MeasureSpec.makeMeasureSpec(
-                    windowManagerOfDisplay?.currentWindowMetrics?.bounds?.width()
-                        ?: windowManager.currentWindowMetrics.bounds.width(),
+                    displayInfo?.logicalWidth ?: windowManager.currentWindowMetrics.bounds.width(),
                     View.MeasureSpec.EXACTLY
                 ),
                 View.MeasureSpec.makeMeasureSpec(
-                    windowManagerOfDisplay?.currentWindowMetrics?.bounds?.height()
+                    displayInfo?.logicalHeight
                         ?: windowManager.currentWindowMetrics.bounds.height(),
                     View.MeasureSpec.EXACTLY
                 ),
@@ -320,7 +322,7 @@
 
     @OptIn(ExperimentalCoroutinesApi::class)
     private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) {
-        val keyguardRootView = KeyguardRootView(previewContext, null).apply { removeAllViews() }
+        val keyguardRootView = KeyguardRootView(previewContext, null)
         disposables.add(
             KeyguardRootViewBinder.bind(
                 keyguardRootView,
@@ -331,6 +333,7 @@
                 keyguardStateController,
                 shadeInteractor,
                 null, // clock provider only needed for burn in
+                null, // jank monitor not required for preview mode
             )
         )
         rootView.addView(
@@ -340,26 +343,29 @@
                 FrameLayout.LayoutParams.MATCH_PARENT,
             ),
         )
-        PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) {
-            if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
-                setupShortcuts(keyguardRootView)
-            }
-            setUpUdfps(previewContext, rootView)
 
-            if (!shouldHideClock) {
-                setUpClock(previewContext, rootView)
-                KeyguardPreviewClockViewBinder.bind(
-                    largeClockHostView,
-                    smallClockHostView,
-                    clockViewModel,
-                )
-            }
+        disposables.add(
+            PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) {
+                if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+                    setupShortcuts(keyguardRootView)
+                }
+                setUpUdfps(previewContext, rootView)
 
-            setUpSmartspace(previewContext, rootView)
-            smartSpaceView?.let {
-                KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel)
+                if (!shouldHideClock) {
+                    setUpClock(previewContext, rootView)
+                    KeyguardPreviewClockViewBinder.bind(
+                        largeClockHostView,
+                        smallClockHostView,
+                        clockViewModel,
+                    )
+                }
+
+                setUpSmartspace(previewContext, rootView)
+                smartSpaceView?.let {
+                    KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel)
+                }
             }
-        }
+        )
     }
 
     private fun setupShortcuts(keyguardRootView: ConstraintLayout) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
new file mode 100644
index 0000000..f7ab1ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view
+
+import android.graphics.PixelFormat
+import android.graphics.Point
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import android.widget.ProgressBar
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+private const val TAG = "SideFpsProgressBar"
+
+const val progressBarHeight = 100
+
+@SysUISingleton
+class SideFpsProgressBar
+@Inject
+constructor(
+    private val layoutInflater: LayoutInflater,
+    private val windowManager: WindowManager,
+) {
+    private var progressBarWidth = 200
+    fun updateView(
+        visible: Boolean,
+        location: Point,
+        shouldRotate90Degrees: Boolean,
+        progressBarWidth: Int
+    ) {
+        if (visible) {
+            this.progressBarWidth = progressBarWidth
+            createAndShowOverlay(location, shouldRotate90Degrees)
+        } else {
+            hideOverlay()
+        }
+    }
+
+    fun hideOverlay() {
+        overlayView = null
+    }
+
+    private val overlayViewParams =
+        WindowManager.LayoutParams(
+                progressBarHeight,
+                progressBarWidth,
+                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+                Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
+                PixelFormat.TRANSPARENT
+            )
+            .apply {
+                title = TAG
+                fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
+                gravity = Gravity.TOP or Gravity.LEFT
+                layoutInDisplayCutoutMode =
+                    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+                privateFlags =
+                    WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY or
+                        WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+            }
+
+    private var overlayView: View? = null
+        set(value) {
+            field?.let { oldView -> windowManager.removeView(oldView) }
+            field = value
+            field?.let { newView -> windowManager.addView(newView, overlayViewParams) }
+        }
+
+    private fun createAndShowOverlay(
+        fingerprintSensorLocation: Point,
+        shouldRotate90Degrees: Boolean
+    ) {
+        if (overlayView == null) {
+            overlayView = layoutInflater.inflate(R.layout.sidefps_progress_bar, null, false)
+        }
+        overlayViewParams.x = fingerprintSensorLocation.x
+        overlayViewParams.y = fingerprintSensorLocation.y
+        if (shouldRotate90Degrees) {
+            overlayView?.rotation = 270.0f
+            overlayViewParams.width = progressBarHeight
+            overlayViewParams.height = progressBarWidth
+        } else {
+            overlayView?.rotation = 0.0f
+            overlayViewParams.width = progressBarWidth
+            overlayViewParams.height = progressBarHeight
+        }
+        windowManager.updateViewLayout(overlayView, overlayViewParams)
+    }
+
+    fun setProgress(value: Float) {
+        overlayView
+            ?.findViewById<ProgressBar?>(R.id.side_fps_progress_bar)
+            ?.setProgress((value * 100).toInt(), false)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
index e8df1a6..d8e4396 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.ui.view.layout.blueprints
 
+import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
 import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
@@ -53,6 +54,7 @@
     splitShadeGuidelines: SplitShadeGuidelines,
     aodNotificationIconsSection: AodNotificationIconsSection,
     aodBurnInSection: AodBurnInSection,
+    communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
 ) : KeyguardBlueprint {
     override val id: String = DEFAULT
 
@@ -69,6 +71,7 @@
             splitShadeGuidelines,
             aodNotificationIconsSection,
             aodBurnInSection,
+            communalTutorialIndicatorSection,
         )
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
new file mode 100644
index 0000000..024707a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
@@ -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.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down AOD->LOCKSCREEN transition into discrete steps for corresponding views to consume.
+ */
+@SysUISingleton
+class AodToLockscreenTransitionViewModel
+@Inject
+constructor(
+    private val interactor: KeyguardTransitionInteractor,
+) {
+
+    private val transitionAnimation =
+        KeyguardTransitionAnimationFlow(
+            transitionDuration = TO_LOCKSCREEN_DURATION,
+            transitionFlow = interactor.aodToLockscreenTransition,
+        )
+
+    /** Ensure alpha is set to be visible */
+    val lockscreenAlpha: Flow<Float> =
+        transitionAnimation.createFlow(
+            duration = 500.milliseconds,
+            onStart = { 1f },
+            onStep = { 1f },
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
new file mode 100644
index 0000000..601dbcc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_AOD_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+
+/** Breaks down GONE->AOD transition into discrete steps for corresponding views to consume. */
+@SysUISingleton
+class GoneToAodTransitionViewModel
+@Inject
+constructor(
+    private val interactor: KeyguardTransitionInteractor,
+) {
+
+    private val transitionAnimation =
+        KeyguardTransitionAnimationFlow(
+            transitionDuration = TO_AOD_DURATION,
+            transitionFlow = interactor.goneToAodTransition,
+        )
+
+    /** y-translation from the top of the screen for AOD */
+    fun enterFromTopTranslationY(translatePx: Int): Flow<Float> {
+        return transitionAnimation.createFlow(
+            startTime = 600.milliseconds,
+            duration = 500.milliseconds,
+            onStart = { translatePx },
+            onStep = { translatePx + it * -translatePx },
+            onFinish = { 0f },
+            onCancel = { 0f },
+            interpolator = EMPHASIZED_DECELERATE,
+        )
+    }
+
+    /** alpha animation upon entering AOD */
+    val enterFromTopAnimationAlpha: Flow<Float> =
+        transitionAnimation.createFlow(
+            startTime = 600.milliseconds,
+            duration = 500.milliseconds,
+            onStart = { 0f },
+            onStep = { it },
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 89835fe..1f98082 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -17,14 +17,19 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.content.Context
 import android.util.MathUtils
+import android.view.View.VISIBLE
 import com.android.app.animation.Interpolators
 import com.android.systemui.common.shared.model.SharedNotificationContainerPosition
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.BurnInModel
-import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.plugins.ClockController
+import com.android.systemui.res.R
 import javax.inject.Inject
 import javax.inject.Provider
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -32,17 +37,23 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
 
 @OptIn(ExperimentalCoroutinesApi::class)
 class KeyguardRootViewModel
 @Inject
 constructor(
+    private val context: Context,
     private val keyguardInteractor: KeyguardInteractor,
     private val burnInInteractor: BurnInInteractor,
+    private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
+    private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
 ) {
 
     data class PreviewMode(val isInPreviewMode: Boolean = false)
@@ -56,9 +67,12 @@
 
     public var clockControllerProvider: Provider<ClockController>? = null
 
-    /** Represents the current state of the KeyguardRootView visibility */
-    val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
-        keyguardInteractor.keyguardRootViewVisibilityState
+    val burnInLayerVisibility: Flow<Int> =
+        keyguardTransitionInteractor.startedKeyguardState
+            .filter { it == AOD || it == LOCKSCREEN }
+            .map { VISIBLE }
+
+    val goneToAodTransition = keyguardTransitionInteractor.goneToAodTransition
 
     /** An observable for the alpha level for the entire keyguard root view. */
     val alpha: Flow<Float> =
@@ -70,9 +84,14 @@
             }
         }
 
-    private val burnIn: Flow<BurnInModel> =
-        combine(keyguardInteractor.dozeAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn
-            ->
+    private fun burnIn(): Flow<BurnInModel> {
+        val dozingAmount: Flow<Float> =
+            merge(
+                keyguardTransitionInteractor.goneToAodTransition.map { it.value },
+                keyguardTransitionInteractor.dozeAmountTransition.map { it.value },
+            )
+
+        return combine(dozingAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn ->
             val interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount)
             val useScaleOnly =
                 clockControllerProvider?.get()?.config?.useAlternateSmartspaceAODTransition ?: false
@@ -91,13 +110,61 @@
                 )
             }
         }
+    }
+
+    /** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */
+    val lockscreenStateAlpha: Flow<Float> = aodToLockscreenTransitionViewModel.lockscreenAlpha
+
+    /** For elements that appear and move during the animation -> AOD */
+    val burnInLayerAlpha: Flow<Float> =
+        previewMode.flatMapLatest {
+            if (it.isInPreviewMode) {
+                flowOf(1f)
+            } else {
+                goneToAodTransitionViewModel.enterFromTopAnimationAlpha
+            }
+        }
 
     val translationY: Flow<Float> =
-        merge(keyguardInteractor.keyguardTranslationY, burnIn.map { it.translationY.toFloat() })
+        previewMode.flatMapLatest {
+            if (it.isInPreviewMode) {
+                flowOf(0f)
+            } else {
+                keyguardInteractor.configurationChange.flatMapLatest { _ ->
+                    val enterFromTopAmount =
+                        context.resources.getDimensionPixelSize(
+                            R.dimen.keyguard_enter_from_top_translation_y
+                        )
+                    combine(
+                        keyguardInteractor.keyguardTranslationY.onStart { emit(0f) },
+                        burnIn().map { it.translationY.toFloat() }.onStart { emit(0f) },
+                        goneToAodTransitionViewModel
+                            .enterFromTopTranslationY(enterFromTopAmount)
+                            .onStart { emit(0f) },
+                    ) { keyguardTransitionY, burnInTranslationY, goneToAodTransitionTranslationY ->
+                        // All 3 values need to be combined for a smooth translation
+                        keyguardTransitionY + burnInTranslationY + goneToAodTransitionTranslationY
+                    }
+                }
+            }
+        }
 
-    val translationX: Flow<Float> = burnIn.map { it.translationX.toFloat() }
+    val translationX: Flow<Float> =
+        previewMode.flatMapLatest {
+            if (it.isInPreviewMode) {
+                flowOf(0f)
+            } else {
+                burnIn().map { it.translationX.toFloat() }
+            }
+        }
 
-    val scale: Flow<Pair<Float, Boolean>> = burnIn.map { Pair(it.scale, it.scaleClockOnly) }
+    val scale: Flow<Pair<Float, Boolean>> =
+        previewMode.flatMapLatest { previewMode ->
+            burnIn().map {
+                val scale = if (previewMode.isInPreviewMode) 1f else it.scale
+                Pair(scale, it.scaleClockOnly)
+            }
+        }
 
     /**
      * Puts this view-model in "preview mode", which means it's being used for UI that is rendering
@@ -105,11 +172,14 @@
      * lock screen.
      */
     fun enablePreviewMode() {
-        val newPreviewMode = PreviewMode(true)
-        previewMode.value = newPreviewMode
+        previewMode.value = PreviewMode(true)
     }
 
     fun onSharedNotificationContainerPositionChanged(top: Float, bottom: Float) {
+        // Notifications should not be visible in preview mode
+        if (previewMode.value.isInPreviewMode) {
+            return
+        }
         keyguardInteractor.sharedNotificationContainerPosition.value =
             SharedNotificationContainerPosition(top, bottom)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
new file mode 100644
index 0000000..2c3b431
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.animation.ValueAnimator
+import android.graphics.Point
+import androidx.core.animation.doOnEnd
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
+import com.android.systemui.biometrics.shared.model.isDefaultOrientation
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class SideFpsProgressBarViewModel
+@Inject
+constructor(
+    private val fpAuthRepository: DeviceEntryFingerprintAuthRepository,
+    private val sfpsSensorInteractor: SideFpsSensorInteractor,
+    displayStateInteractor: DisplayStateInteractor,
+    @Application private val applicationScope: CoroutineScope,
+) {
+    private val _progress = MutableStateFlow(0.0f)
+    private val _visible = MutableStateFlow(false)
+    private var _animator: ValueAnimator? = null
+
+    private fun onFingerprintCaptureCompleted() {
+        _visible.value = false
+        _progress.value = 0.0f
+    }
+
+    val isVisible: Flow<Boolean> = _visible.asStateFlow()
+
+    val progress: Flow<Float> = _progress.asStateFlow()
+
+    val sensorWidth: Flow<Int> = sfpsSensorInteractor.sensorLocation.map { it.width }
+
+    val sensorLocation: Flow<Point> =
+        sfpsSensorInteractor.sensorLocation.map { Point(it.left, it.top) }
+
+    val isFingerprintAuthRunning: Flow<Boolean> = fpAuthRepository.isRunning
+
+    val shouldRotate90Degrees: Flow<Boolean> =
+        combine(displayStateInteractor.currentRotation, sfpsSensorInteractor.sensorLocation, ::Pair)
+            .map { (rotation, sensorLocation) ->
+                if (rotation.isDefaultOrientation()) {
+                    sensorLocation.isSensorVerticalInDefaultOrientation
+                } else {
+                    !sensorLocation.isSensorVerticalInDefaultOrientation
+                }
+            }
+
+    val isProlongedTouchRequiredForAuthentication: Flow<Boolean> =
+        sfpsSensorInteractor.isProlongedTouchRequiredForAuthentication
+
+    init {
+        applicationScope.launch {
+            combine(
+                    sfpsSensorInteractor.isProlongedTouchRequiredForAuthentication,
+                    sfpsSensorInteractor.authenticationDuration,
+                    ::Pair
+                )
+                .collectLatest { (enabled, authDuration) ->
+                    if (!enabled) return@collectLatest
+
+                    launch {
+                        fpAuthRepository.authenticationStatus.collectLatest { authStatus ->
+                            when (authStatus) {
+                                is AcquiredFingerprintAuthenticationStatus -> {
+                                    if (authStatus.fingerprintCaptureStarted) {
+
+                                        _visible.value = true
+                                        _animator?.cancel()
+                                        _animator =
+                                            ValueAnimator.ofFloat(0.0f, 1.0f)
+                                                .setDuration(authDuration)
+                                                .apply {
+                                                    addUpdateListener {
+                                                        _progress.value = it.animatedValue as Float
+                                                    }
+                                                    addListener(
+                                                        doOnEnd {
+                                                            if (_progress.value == 0.0f) {
+                                                                _visible.value = false
+                                                            }
+                                                        }
+                                                    )
+                                                }
+                                        _animator?.start()
+                                    } else if (authStatus.fingerprintCaptureCompleted) {
+                                        onFingerprintCaptureCompleted()
+                                    } else {
+                                        // Abandoned FP Auth attempt
+                                        _animator?.reverse()
+                                    }
+                                }
+                                is ErrorFingerprintAuthenticationStatus ->
+                                    onFingerprintCaptureCompleted()
+                                is FailFingerprintAuthenticationStatus ->
+                                    onFingerprintCaptureCompleted()
+                                is SuccessFingerprintAuthenticationStatus ->
+                                    onFingerprintCaptureCompleted()
+                                else -> Unit
+                            }
+                        }
+                    }
+                }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
index 929f27f..dca151d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
@@ -17,20 +17,10 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.graphics.Rect
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 @ExperimentalCoroutinesApi
-class UdfpsKeyguardViewModel
-@Inject
-constructor(
-    private val featureFlags: FeatureFlags,
-) {
+class UdfpsKeyguardViewModel @Inject constructor() {
     var sensorBounds: Rect = Rect()
-
-    fun useExpandedOverlay(): Boolean {
-        return featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
index 3d4fca1..1b3b473 100644
--- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
@@ -51,11 +51,12 @@
         Uri uri;
         boolean looping;
         AudioAttributes attributes;
+        float volume;
         long requestTime;
 
         public String toString() {
             return "{ code=" + code + " looping=" + looping + " attributes=" + attributes
-                    + " uri=" + uri + " }";
+                    + " volume=" + volume + " uri=" + uri + " }";
         }
     }
 
@@ -101,6 +102,7 @@
                     player.setAudioAttributes(mCmd.attributes);
                     player.setDataSource(mCmd.context, mCmd.uri);
                     player.setLooping(mCmd.looping);
+                    player.setVolume(mCmd.volume);
                     player.setOnCompletionListener(NotificationPlayer.this);
                     player.setOnErrorListener(NotificationPlayer.this);
                     player.prepare();
@@ -401,10 +403,11 @@
      *          (see {@link MediaPlayer#setLooping(boolean)})
      * @param stream the AudioStream to use.
      *          (see {@link MediaPlayer#setAudioStreamType(int)})
+     * @param volume the volume for the audio with values in range [0.0, 1.0]
      * @deprecated use {@link #play(Context, Uri, boolean, AudioAttributes)} instead.
      */
     @Deprecated
-    public void play(Context context, Uri uri, boolean looping, int stream) {
+    public void play(Context context, Uri uri, boolean looping, int stream, float volume) {
         if (DEBUG) { Log.d(mTag, "play uri=" + uri.toString()); }
         PlayerBase.deprecateStreamTypeForPlayback(stream, "NotificationPlayer", "play");
         Command cmd = new Command();
@@ -414,6 +417,7 @@
         cmd.uri = uri;
         cmd.looping = looping;
         cmd.attributes = new AudioAttributes.Builder().setInternalLegacyStreamType(stream).build();
+        cmd.volume = volume;
         synchronized (mCmdQueue) {
             enqueueLocked(cmd);
             mState = PLAY;
@@ -432,8 +436,10 @@
      *          (see {@link MediaPlayer#setLooping(boolean)})
      * @param attributes the AudioAttributes to use.
      *          (see {@link MediaPlayer#setAudioAttributes(AudioAttributes)})
+     * @param volume the volume for the audio with values in range [0.0, 1.0]
      */
-    public void play(Context context, Uri uri, boolean looping, AudioAttributes attributes) {
+    public void play(Context context, Uri uri, boolean looping, AudioAttributes attributes,
+            float volume) {
         if (DEBUG) { Log.d(mTag, "play uri=" + uri.toString()); }
         Command cmd = new Command();
         cmd.requestTime = SystemClock.uptimeMillis();
@@ -442,6 +448,7 @@
         cmd.uri = uri;
         cmd.looping = looping;
         cmd.attributes = attributes;
+        cmd.volume = volume;
         synchronized (mCmdQueue) {
             enqueueLocked(cmd);
             mState = PLAY;
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index 80be766..7a48836 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -285,7 +285,8 @@
         }
 
         @Override
-        public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) {
+        public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa,
+                float volume) {
             if (LOGD) Log.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")");
             if (Binder.getCallingUid() != Process.SYSTEM_UID) {
                 throw new SecurityException("Async playback only available from system UID.");
@@ -293,7 +294,7 @@
             if (UserHandle.ALL.equals(user)) {
                 user = UserHandle.SYSTEM;
             }
-            mAsyncPlayer.play(getContextForUser(user), uri, looping, aa);
+            mAsyncPlayer.play(getContextForUser(user), uri, looping, aa, volume);
         }
 
         @Override
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/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 0f3e0ac..2a32ddf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -60,7 +60,6 @@
 import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.Dumpable
-import com.android.systemui.res.R
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -83,6 +82,7 @@
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
 import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
 import com.android.systemui.statusbar.notification.row.HybridGroupManager
@@ -90,6 +90,7 @@
 import com.android.systemui.util.Assert
 import com.android.systemui.util.Utils
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.ThreadFactory
 import com.android.systemui.util.time.SystemClock
 import com.android.systemui.util.traceSection
 import java.io.IOException
@@ -245,7 +246,7 @@
     @Inject
     constructor(
         context: Context,
-        @Background backgroundExecutor: Executor,
+        threadFactory: ThreadFactory,
         @Main uiExecutor: Executor,
         @Main foregroundExecutor: DelayableExecutor,
         mediaControllerFactory: MediaControllerFactory,
@@ -267,7 +268,9 @@
         keyguardUpdateMonitor: KeyguardUpdateMonitor,
     ) : this(
         context,
-        backgroundExecutor,
+        // Loading bitmap for UMO background can take longer time, so it cannot run on the default
+        // background thread. Use a custom thread for media.
+        threadFactory.buildExecutorOnNewThread(TAG),
         uiExecutor,
         foregroundExecutor,
         mediaControllerFactory,
@@ -1429,8 +1432,6 @@
     }
 
     private fun onSessionDestroyed(key: String) {
-        if (!mediaFlags.isRetainingPlayersEnabled()) return
-
         if (DEBUG) Log.d(TAG, "session destroyed for $key")
         val entry = mediaEntries.remove(key) ?: return
         // Clear token since the session is no longer valid
@@ -1474,7 +1475,7 @@
             if (DEBUG) Log.d(TAG, "Removing still-active player $key")
             notifyMediaDataRemoved(key)
             logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
-        } else {
+        } else if (mediaFlags.isRetainingPlayersEnabled() || isAbleToResume(removed)) {
             // Convert to resume
             if (DEBUG) {
                 Log.d(
@@ -1484,6 +1485,11 @@
                 )
             }
             convertToResumePlayer(key, removed)
+        } else {
+            // Retaining players flag is off and app doesn't support resume: remove player.
+            if (DEBUG) Log.d(TAG, "Removing player $key")
+            notifyMediaDataRemoved(key)
+            logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
index 1fe93ed..1db31ae 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
@@ -21,6 +21,7 @@
 import android.content.Context
 import android.graphics.drawable.Drawable
 import android.media.MediaRouter2Manager
+import android.media.RoutingSessionInfo
 import android.media.session.MediaController
 import android.text.TextUtils
 import android.util.Log
@@ -31,17 +32,20 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.settingslib.media.LocalMediaManager
 import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.media.PhoneMediaDevice
 import com.android.systemui.Dumpable
-import com.android.systemui.res.R
 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.media.controls.models.player.MediaData
 import com.android.systemui.media.controls.models.player.MediaDeviceData
 import com.android.systemui.media.controls.util.MediaControllerFactory
 import com.android.systemui.media.controls.util.MediaDataUtils
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.ConfigurationController
 import java.io.PrintWriter
 import java.util.concurrent.Executor
@@ -64,7 +68,8 @@
     private val localBluetoothManager: LocalBluetoothManager?,
     @Main private val fgExecutor: Executor,
     @Background private val bgExecutor: Executor,
-    dumpManager: DumpManager
+    dumpManager: DumpManager,
+    private val featureFlags: FeatureFlagsClassic,
 ) : MediaDataManager.Listener, Dumpable {
 
     private val listeners: MutableSet<Listener> = mutableSetOf()
@@ -215,6 +220,7 @@
                 println("    volumeControlId=$volumeControlId cached= $playbackVolumeControlId")
                 println("    routingSession=$routingSession")
                 println("    selectedRoutes=$selectedRoutes")
+                println("    currentConnectedDevice=${localMediaManager.currentConnectedDevice}")
             }
         }
 
@@ -348,16 +354,16 @@
                 }
                 val device =
                     aboutToConnect?.fullMediaDevice ?: localMediaManager.currentConnectedDevice
-                val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
+                val routingSession =
+                    controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
 
                 // If we have a controller but get a null route, then don't trust the device
-                val enabled = device != null && (controller == null || route != null)
-                val name =
-                    if (controller == null || route != null) {
-                        route?.name?.toString() ?: device?.name
-                    } else {
-                        null
-                    }
+                val enabled = device != null && (controller == null || routingSession != null)
+
+                val name = getDeviceName(device, routingSession)
+                if (DEBUG) {
+                    Log.d(TAG, "new device name $name")
+                }
                 current =
                     MediaDeviceData(
                         enabled,
@@ -369,6 +375,57 @@
             }
         }
 
+        /** Return a display name for the current device / route, or null if not possible */
+        private fun getDeviceName(
+            device: MediaDevice?,
+            routingSession: RoutingSessionInfo?,
+        ): String? {
+            val selectedRoutes = routingSession?.let { mr2manager.getSelectedRoutes(it) }
+
+            if (DEBUG) {
+                Log.d(
+                    TAG,
+                    "device is $device, controller $controller," +
+                        " routingSession ${routingSession?.name}" +
+                        " or ${selectedRoutes?.firstOrNull()?.name}"
+                )
+            }
+
+            if (!featureFlags.isEnabled(Flags.MEDIA_DEVICE_NAME_FIX)) {
+                if (controller == null || routingSession != null) {
+                    return routingSession?.name?.toString() ?: device?.name
+                }
+                return null
+            }
+
+            if (controller == null) {
+                // In resume state, we don't have a controller - just use the device name
+                return device?.name
+            }
+
+            if (routingSession == null) {
+                // This happens when casting from apps that do not support MediaRouter2
+                // The output switcher can't show anything useful here, so set to null
+                return null
+            }
+
+            // If this is a user route (app / cast provided), use the provided name
+            if (!routingSession.isSystemSession) {
+                return routingSession.name?.toString() ?: device?.name
+            }
+
+            selectedRoutes?.firstOrNull()?.let {
+                if (device is PhoneMediaDevice) {
+                    // Get the (localized) name for this phone device
+                    return PhoneMediaDevice.getSystemRouteNameFromType(context, it)
+                } else {
+                    // If it's another type of device (in practice, Bluetooth), use the route name
+                    return it.name.toString()
+                }
+            }
+            return null
+        }
+
         private fun isLeAudioBroadcastEnabled(): Boolean {
             if (localBluetoothManager != null) {
                 val profileManager = localBluetoothManager.profileManager
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/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 72aea04..2217509 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -33,6 +33,8 @@
 import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
 import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider
+import com.android.systemui.mediaprojection.appselector.view.WindowMetricsProvider
+import com.android.systemui.mediaprojection.appselector.view.WindowMetricsProviderImpl
 import com.android.systemui.mediaprojection.devicepolicy.MediaProjectionDevicePolicyModule
 import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile
 import com.android.systemui.mediaprojection.permission.MediaProjectionPermissionActivity
@@ -106,6 +108,8 @@
         impl: TaskPreviewSizeProvider
     ): DefaultLifecycleObserver
 
+    @Binds fun windowMetricsProvider(impl: WindowMetricsProviderImpl): WindowMetricsProvider
+
     companion object {
         @Provides
         @MediaProjectionAppSelector
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index e61650f..fced117 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -20,10 +20,15 @@
 import android.os.UserHandle
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
+import com.android.systemui.shared.recents.model.ThumbnailData
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.async
 import kotlinx.coroutines.cancel
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 
 @MediaProjectionAppSelectorScope
@@ -36,7 +41,8 @@
     @HostUserHandle private val hostUserHandle: UserHandle,
     @MediaProjectionAppSelector private val scope: CoroutineScope,
     @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName,
-    @MediaProjectionAppSelector private val callerPackageName: String?
+    @MediaProjectionAppSelector private val callerPackageName: String?,
+    private val thumbnailLoader: RecentTaskThumbnailLoader,
 ) {
 
     fun init() {
@@ -46,6 +52,11 @@
             val tasks =
                 recentTasks.filterDevicePolicyRestrictedTasks().filterAppSelector().sortedTasks()
 
+            // Thumbnails are not fresh for the foreground task(s). They are only refreshed at
+            // launch, going to home, or going to overview.
+            // For this reason, we need to refresh them here.
+            refreshForegroundTaskThumbnails(tasks)
+
             view.bind(tasks)
         }
     }
@@ -54,6 +65,16 @@
         scope.cancel()
     }
 
+    private suspend fun refreshForegroundTaskThumbnails(tasks: List<RecentTask>) {
+        coroutineScope {
+            val thumbnails: List<Deferred<ThumbnailData?>> =
+                tasks
+                    .filter { it.isForegroundTask }
+                    .map { async { thumbnailLoader.captureThumbnail(it.taskId) } }
+            thumbnails.forEach { thumbnail -> thumbnail.await() }
+        }
+    }
+
     /** Removes all recent tasks that should be blocked according to the policy */
     private fun List<RecentTask>.filterDevicePolicyRestrictedTasks(): List<RecentTask> = filter {
         devicePolicyResolver.isScreenCaptureAllowed(
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index 41e2286..a9e6c53 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -25,5 +25,6 @@
     @UserIdInt val userId: Int,
     val topActivityComponent: ComponentName?,
     val baseIntentComponent: ComponentName?,
-    @ColorInt val colorBackground: Int?
+    @ColorInt val colorBackground: Int?,
+    val isForegroundTask: Boolean,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index 01398cf..aa4c4e5 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -48,9 +48,14 @@
 
     override suspend fun loadRecentTasks(): List<RecentTask> =
         withContext(coroutineDispatcher) {
-            val rawRecentTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList()
-
-            rawRecentTasks
+            val groupedTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList()
+            // Note: the returned task list is from the most-recent to least-recent order.
+            // The last foreground task is at index 1, because at index 0 will be our app selector.
+            val foregroundGroup = groupedTasks.elementAtOrNull(1)
+            val foregroundTaskId1 = foregroundGroup?.taskInfo1?.taskId
+            val foregroundTaskId2 = foregroundGroup?.taskInfo2?.taskId
+            val foregroundTaskIds = listOfNotNull(foregroundTaskId1, foregroundTaskId2)
+            groupedTasks
                 .flatMap { listOfNotNull(it.taskInfo1, it.taskInfo2) }
                 .map {
                     RecentTask(
@@ -58,7 +63,8 @@
                         it.userId,
                         it.topActivity,
                         it.baseIntent?.component,
-                        it.taskDescription?.backgroundColor
+                        it.taskDescription?.backgroundColor,
+                        isForegroundTask = it.taskId in foregroundTaskIds
                     )
                 }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt
index 47faaed..ccf272c 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt
@@ -25,6 +25,8 @@
 
 interface RecentTaskThumbnailLoader {
     suspend fun loadThumbnail(taskId: Int): ThumbnailData?
+
+    suspend fun captureThumbnail(taskId: Int): ThumbnailData?
 }
 
 class ActivityTaskManagerThumbnailLoader
@@ -36,8 +38,13 @@
 
     override suspend fun loadThumbnail(taskId: Int): ThumbnailData? =
         withContext(coroutineDispatcher) {
-            val thumbnailData =
-                activityManager.getTaskThumbnail(taskId, /* isLowResolution= */ false)
-            if (thumbnailData.thumbnail == null) null else thumbnailData
+            activityManager.getTaskThumbnail(taskId, /* isLowResolution= */ false).takeIf {
+                it.thumbnail != null
+            }
+        }
+
+    override suspend fun captureThumbnail(taskId: Int): ThumbnailData? =
+        withContext(coroutineDispatcher) {
+            activityManager.takeTaskThumbnail(taskId).takeIf { it.thumbnail != null }
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
index 864d35a..c829471 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
@@ -19,8 +19,6 @@
 import android.content.Context
 import android.content.res.Configuration
 import android.graphics.Rect
-import android.view.WindowInsets.Type
-import android.view.WindowManager
 import androidx.lifecycle.DefaultLifecycleObserver
 import androidx.lifecycle.LifecycleOwner
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
@@ -36,7 +34,7 @@
 @Inject
 constructor(
     private val context: Context,
-    private val windowManager: WindowManager,
+    private val windowMetricsProvider: WindowMetricsProvider,
     private val configurationController: ConfigurationController,
 ) : CallbackController<TaskPreviewSizeListener>, ConfigurationListener, DefaultLifecycleObserver {
 
@@ -62,17 +60,14 @@
     }
 
     private fun calculateSize(): Rect {
-        val windowMetrics = windowManager.maximumWindowMetrics
-        val maximumWindowHeight = windowMetrics.bounds.height()
-        val width = windowMetrics.bounds.width()
+        val maxWindowBounds = windowMetricsProvider.maximumWindowBounds
+        val maximumWindowHeight = maxWindowBounds.height()
+        val width = maxWindowBounds.width()
         var height = maximumWindowHeight
 
         val isLargeScreen = isLargeScreen(context)
         if (isLargeScreen) {
-            val taskbarSize =
-                windowManager.currentWindowMetrics.windowInsets
-                    .getInsets(Type.tappableElement())
-                    .bottom
+            val taskbarSize = windowMetricsProvider.currentWindowInsets.bottom
             height -= taskbarSize
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProvider.kt
new file mode 100644
index 0000000..1932920
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProvider.kt
@@ -0,0 +1,30 @@
+package com.android.systemui.mediaprojection.appselector.view
+
+import android.graphics.Insets
+import android.graphics.Rect
+import android.view.WindowInsets
+import android.view.WindowManager
+import javax.inject.Inject
+
+/** Provides values related to window metrics. */
+interface WindowMetricsProvider {
+
+    val maximumWindowBounds: Rect
+
+    val currentWindowInsets: Insets
+}
+
+class WindowMetricsProviderImpl
+@Inject
+constructor(
+    private val windowManager: WindowManager,
+) : WindowMetricsProvider {
+    override val maximumWindowBounds: Rect
+        get() = windowManager.maximumWindowMetrics.bounds
+
+    override val currentWindowInsets: Insets
+        get() =
+            windowManager.currentWindowMetrics.windowInsets.getInsets(
+                WindowInsets.Type.tappableElement()
+            )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 2b56d0c..d08d040 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -239,6 +239,8 @@
     protected void onDestroy() {
         super.onDestroy();
         if (mDialog != null) {
+            mDialog.setOnDismissListener(null);
+            mDialog.setOnCancelListener(null);
             mDialog.dismiss();
         }
     }
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/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
index 07846b5..3cdcb2c 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
@@ -24,6 +24,8 @@
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shared.system.QuickStepContract;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -108,6 +110,7 @@
         }
     }
 
+    @NeverCompile
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println("SysUiState state:");
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index 564e984..2928cce 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -65,6 +65,8 @@
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.pip.Pip;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 import java.io.PrintWriter;
 import java.util.Optional;
 
@@ -476,6 +478,7 @@
         return mNavigationBars.get(mDisplayTracker.getDefaultDisplayId());
     }
 
+    @NeverCompile
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("mIsLargeScreen=" + mIsLargeScreen);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 4d6d95a..bc4f7f25 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -584,7 +584,6 @@
         if (!visible) {
             mTransitionListener.onBackAltCleared();
         }
-        mRotationButtonController.getRotationButton().setCanShowRotationButton(!visible);
     }
 
     void setDisabledFlags(int disabledFlags, SysUiState sysUiState) {
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
index c567d56..10a2b3c 100644
--- a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
@@ -31,10 +31,10 @@
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.res.R
 import com.android.systemui.people.PeopleSpaceTileView
 import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import com.android.systemui.res.R
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
@@ -101,10 +101,10 @@
                                 view,
                                 priorityTiles,
                                 recentTiles,
-                                viewModel::onTileClicked,
+                                viewModel.onTileClicked,
                             )
                         } else {
-                            setNoConversationsContent(view, viewModel::onUserJourneyCancelled)
+                            setNoConversationsContent(view, viewModel.onUserJourneyCancelled)
                         }
                     }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
index ed7c21b..b847e95 100644
--- a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
@@ -23,35 +23,32 @@
 import android.util.Log
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
-import com.android.systemui.res.R
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.people.PeopleSpaceUtils
 import com.android.systemui.people.PeopleTileViewHelper
 import com.android.systemui.people.data.model.PeopleTileModel
 import com.android.systemui.people.data.repository.PeopleTileRepository
 import com.android.systemui.people.data.repository.PeopleWidgetRepository
+import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
+private const val TAG = "PeopleViewModel"
+
 /**
  * Models UI state for the people space, allowing the user to select which conversation should be
  * associated to a new or existing Conversation widget.
  */
 class PeopleViewModel(
-    @Application private val context: Context,
-    private val tileRepository: PeopleTileRepository,
-    private val widgetRepository: PeopleWidgetRepository,
-) : ViewModel() {
     /**
      * The list of the priority tiles/conversations.
      *
      * Important: Even though this is a Flow, the underlying API used to populate this Flow is not
      * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
      */
-    private val _priorityTiles = MutableStateFlow(priorityTiles())
-    val priorityTiles: StateFlow<List<PeopleTileViewModel>> = _priorityTiles.asStateFlow()
+    val priorityTiles: StateFlow<List<PeopleTileViewModel>>,
 
     /**
      * The list of the priority tiles/conversations.
@@ -59,84 +56,29 @@
      * Important: Even though this is a Flow, the underlying API used to populate this Flow is not
      * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
      */
-    private val _recentTiles = MutableStateFlow(recentTiles())
-    val recentTiles: StateFlow<List<PeopleTileViewModel>> = _recentTiles.asStateFlow()
+    val recentTiles: StateFlow<List<PeopleTileViewModel>>,
 
     /** The ID of the widget currently being edited/added. */
-    private val _appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID)
-    val appWidgetId: StateFlow<Int> = _appWidgetId.asStateFlow()
+    val appWidgetId: StateFlow<Int>,
 
     /** The result of this user journey. */
-    private val _result = MutableStateFlow<Result?>(null)
-    val result: StateFlow<Result?> = _result.asStateFlow()
+    val result: StateFlow<Result?>,
 
     /** Refresh the [priorityTiles] and [recentTiles]. */
-    fun onTileRefreshRequested() {
-        _priorityTiles.value = priorityTiles()
-        _recentTiles.value = recentTiles()
-    }
+    val onTileRefreshRequested: () -> Unit,
 
     /** Called when the [appWidgetId] should be changed to [widgetId]. */
-    fun onWidgetIdChanged(widgetId: Int) {
-        _appWidgetId.value = widgetId
-    }
+    val onWidgetIdChanged: (widgetId: Int) -> Unit,
 
     /** Clear [result], setting it to null. */
-    fun clearResult() {
-        _result.value = null
-    }
+    val clearResult: () -> Unit,
 
     /** Called when a tile is clicked. */
-    fun onTileClicked(tile: PeopleTileViewModel) {
-        val widgetId = _appWidgetId.value
-        if (PeopleSpaceUtils.DEBUG) {
-            Log.d(
-                TAG,
-                "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID $widgetId"
-            )
-        }
-        widgetRepository.setWidgetTile(widgetId, tile.key)
-        _result.value =
-            Result.Success(Intent().apply { putExtra(EXTRA_APPWIDGET_ID, appWidgetId.value) })
-    }
+    val onTileClicked: (tile: PeopleTileViewModel) -> Unit,
 
     /** Called when this user journey is cancelled. */
-    fun onUserJourneyCancelled() {
-        _result.value = Result.Cancelled
-    }
-
-    private fun priorityTiles(): List<PeopleTileViewModel> {
-        return try {
-            tileRepository.priorityTiles().map { it.toViewModel() }
-        } catch (e: Exception) {
-            Log.e(TAG, "Couldn't retrieve priority conversations", e)
-            emptyList()
-        }
-    }
-
-    private fun recentTiles(): List<PeopleTileViewModel> {
-        return try {
-            tileRepository.recentTiles().map { it.toViewModel() }
-        } catch (e: Exception) {
-            Log.e(TAG, "Couldn't retrieve recent conversations", e)
-            emptyList()
-        }
-    }
-
-    private fun PeopleTileModel.toViewModel(): PeopleTileViewModel {
-        val icon =
-            PeopleTileViewHelper.getPersonIconBitmap(
-                context,
-                this,
-                PeopleTileViewHelper.getSizeInDp(
-                    context,
-                    R.dimen.avatar_size_for_medium,
-                    context.resources.displayMetrics.density,
-                )
-            )
-        return PeopleTileViewModel(key, icon, username)
-    }
-
+    val onUserJourneyCancelled: () -> Unit,
+) : ViewModel() {
     /** The Factory that should be used to create a [PeopleViewModel]. */
     class Factory
     @Inject
@@ -153,10 +95,94 @@
 
     sealed class Result {
         class Success(val data: Intent) : Result()
+
         object Cancelled : Result()
     }
+}
 
-    companion object {
-        private const val TAG = "PeopleViewModel"
+private fun PeopleViewModel(
+    @Application context: Context,
+    tileRepository: PeopleTileRepository,
+    widgetRepository: PeopleWidgetRepository,
+): PeopleViewModel {
+    fun priorityTiles(): List<PeopleTileViewModel> {
+        return try {
+            tileRepository.priorityTiles().map { it.toViewModel(context) }
+        } catch (e: Exception) {
+            Log.e(TAG, "Couldn't retrieve priority conversations", e)
+            emptyList()
+        }
     }
+
+    fun recentTiles(): List<PeopleTileViewModel> {
+        return try {
+            tileRepository.recentTiles().map { it.toViewModel(context) }
+        } catch (e: Exception) {
+            Log.e(TAG, "Couldn't retrieve recent conversations", e)
+            emptyList()
+        }
+    }
+
+    val priorityTiles = MutableStateFlow(priorityTiles())
+    val recentTiles = MutableStateFlow(recentTiles())
+    val appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID)
+    val result = MutableStateFlow<PeopleViewModel.Result?>(null)
+
+    fun onTileRefreshRequested() {
+        priorityTiles.value = priorityTiles()
+        recentTiles.value = recentTiles()
+    }
+
+    fun onWidgetIdChanged(widgetId: Int) {
+        appWidgetId.value = widgetId
+    }
+
+    fun clearResult() {
+        result.value = null
+    }
+
+    fun onTileClicked(tile: PeopleTileViewModel) {
+        val widgetId = appWidgetId.value
+        if (PeopleSpaceUtils.DEBUG) {
+            Log.d(
+                TAG,
+                "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID $widgetId"
+            )
+        }
+        widgetRepository.setWidgetTile(widgetId, tile.key)
+        result.value =
+            PeopleViewModel.Result.Success(
+                Intent().apply { putExtra(EXTRA_APPWIDGET_ID, appWidgetId.value) }
+            )
+    }
+
+    fun onUserJourneyCancelled() {
+        result.value = PeopleViewModel.Result.Cancelled
+    }
+
+    return PeopleViewModel(
+        priorityTiles = priorityTiles.asStateFlow(),
+        recentTiles = recentTiles.asStateFlow(),
+        appWidgetId = appWidgetId.asStateFlow(),
+        result = result.asStateFlow(),
+        onTileRefreshRequested = ::onTileRefreshRequested,
+        onWidgetIdChanged = ::onWidgetIdChanged,
+        clearResult = ::clearResult,
+        onTileClicked = ::onTileClicked,
+        onUserJourneyCancelled = ::onUserJourneyCancelled,
+    )
+}
+
+fun PeopleTileModel.toViewModel(@Application context: Context): PeopleTileViewModel {
+    val icon =
+        PeopleTileViewHelper.getPersonIconBitmap(
+            context,
+            this,
+            PeopleTileViewHelper.getSizeInDp(
+                context,
+                R.dimen.avatar_size_for_medium,
+                context.resources.displayMetrics.density,
+            )
+        )
+    return PeopleTileViewModel(key, icon, username)
 }
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/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index 202254b..4aad6a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -72,6 +72,8 @@
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
 import com.android.systemui.util.Utils;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.function.Consumer;
@@ -934,6 +936,7 @@
         return mListeningAndVisibilityLifecycleOwner;
     }
 
+    @NeverCompile
     @Override
     public void dump(PrintWriter pw, String[] args) {
         IndentingPrintWriter indentingPw = new IndentingPrintWriter(pw, /* singleIndent= */ "  ");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
index 7794fa0..eb11568 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
@@ -20,15 +20,14 @@
 import android.os.Handler;
 
 import com.android.systemui.statusbar.policy.Listenable;
-import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.settings.SettingsProxy;
 import com.android.systemui.util.settings.SystemSettings;
 
 /**
- * Helper for managing secure, global, and system settings through use of {@link SettingsProxy},
- * which is the common superclass of {@link SecureSettings}, {@link GlobalSettings}, and
- * {@link SystemSettings}.
+ * Helper for managing global settings through use of {@link SettingsProxy}. This should
+ * <em>not</em> be used for {@link SecureSettings} or {@link SystemSettings} since those must be
+ * user-aware (instead, use {@link UserSettingObserver}).
  */
 public abstract class SettingObserver extends ContentObserver implements Listenable {
     private final SettingsProxy mSettingsProxy;
@@ -36,23 +35,20 @@
     private final int mDefaultValue;
 
     private boolean mListening;
-    private int mUserId;
     private int mObservedValue;
 
     protected abstract void handleValueChanged(int value, boolean observedChange);
 
-    public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName,
-            int userId) {
-        this(settingsProxy, handler, settingName, userId, 0);
+    public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName) {
+        this(settingsProxy, handler, settingName, 0);
     }
 
     public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName,
-            int userId, int defaultValue) {
+            int defaultValue) {
         super(handler);
         mSettingsProxy = settingsProxy;
         mSettingName = settingName;
         mObservedValue = mDefaultValue = defaultValue;
-        mUserId = userId;
     }
 
     public int getValue() {
@@ -65,11 +61,11 @@
      * @param value The new value for the setting.
      */
     public void setValue(int value) {
-        mSettingsProxy.putIntForUser(mSettingName, value, mUserId);
+        mSettingsProxy.putInt(mSettingName, value);
     }
 
     private int getValueFromProvider() {
-        return mSettingsProxy.getIntForUser(mSettingName, mDefaultValue, mUserId);
+        return mSettingsProxy.getInt(mSettingName, mDefaultValue);
     }
 
     @Override
@@ -78,8 +74,8 @@
         mListening = listening;
         if (listening) {
             mObservedValue = getValueFromProvider();
-            mSettingsProxy.registerContentObserverForUser(
-                    mSettingsProxy.getUriFor(mSettingName), false, this, mUserId);
+            mSettingsProxy.registerContentObserver(
+                    mSettingsProxy.getUriFor(mSettingName), false, this);
         } else {
             mSettingsProxy.unregisterContentObserver(this);
             mObservedValue = mDefaultValue;
@@ -94,21 +90,6 @@
         handleValueChanged(value, changed);
     }
 
-    /**
-     * Set user handle for which to observe the setting.
-     */
-    public void setUserId(int userId) {
-        mUserId = userId;
-        if (mListening) {
-            setListening(false);
-            setListening(true);
-        }
-    }
-
-    public int getCurrentUser() {
-        return mUserId;
-    }
-
     public String getKey() {
         return mSettingName;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt
index 11759f7..777faea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt
@@ -19,7 +19,7 @@
 import android.content.Context
 import android.util.AttributeSet
 import com.android.systemui.flags.Flags
-import com.android.systemui.flags.ViewRefactorFlag
+import com.android.systemui.flags.RefactorFlag
 import com.android.systemui.res.R
 
 open class SideLabelTileLayout(
@@ -27,8 +27,8 @@
     attrs: AttributeSet?
 ) : TileLayout(context, attrs) {
 
-    private final val isSmallLandscapeLockscreenEnabled =
-            ViewRefactorFlag(flag = Flags.LOCKSCREEN_ENABLE_LANDSCAPE).isEnabled
+    private val isSmallLandscapeLockscreenEnabled =
+            RefactorFlag.forView(Flags.LOCKSCREEN_ENABLE_LANDSCAPE).isEnabled
 
     override fun updateResources(): Boolean {
         return super.updateResources().also {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 9ba1645..9d4eba5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -15,13 +15,13 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.FontSizeUtils;
-import com.android.systemui.flags.ViewRefactorFlag;
-import com.android.systemui.res.R;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.RefactorFlag;
 import com.android.systemui.qs.QSPanel.QSTileLayout;
 import com.android.systemui.qs.QSPanelControllerBase.TileRecord;
 import com.android.systemui.qs.tileimpl.HeightOverrideable;
 import com.android.systemui.qs.tileimpl.QSTileViewImplKt;
+import com.android.systemui.res.R;
 
 import java.util.ArrayList;
 
@@ -55,7 +55,7 @@
     protected int mLastTileBottom;
     protected TextView mTempTextView;
     private final Boolean mIsSmallLandscapeLockscreenEnabled =
-            new ViewRefactorFlag(Flags.LOCKSCREEN_ENABLE_LANDSCAPE).isEnabled();
+            RefactorFlag.forView(Flags.LOCKSCREEN_ENABLE_LANDSCAPE).isEnabled();
 
     public TileLayout(Context context) {
         this(context, null);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java
new file mode 100644
index 0000000..539c2d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import android.database.ContentObserver;
+import android.os.Handler;
+
+import com.android.systemui.statusbar.policy.Listenable;
+import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.settings.SystemSettings;
+import com.android.systemui.util.settings.UserSettingsProxy;
+
+/**
+ * Helper for managing secure and system settings through use of {@link UserSettingsProxy},
+ * which is the common superclass of {@link SecureSettings} and {@link SystemSettings}.
+ */
+public abstract class UserSettingObserver extends ContentObserver implements Listenable {
+    private final UserSettingsProxy mSettingsProxy;
+    private final String mSettingName;
+    private final int mDefaultValue;
+
+    private boolean mListening;
+    private int mUserId;
+    private int mObservedValue;
+
+    protected abstract void handleValueChanged(int value, boolean observedChange);
+
+    public UserSettingObserver(UserSettingsProxy settingsProxy, Handler handler, String settingName,
+            int userId) {
+        this(settingsProxy, handler, settingName, userId, 0);
+    }
+
+    public UserSettingObserver(UserSettingsProxy settingsProxy, Handler handler, String settingName,
+            int userId, int defaultValue) {
+        super(handler);
+        mSettingsProxy = settingsProxy;
+        mSettingName = settingName;
+        mObservedValue = mDefaultValue = defaultValue;
+        mUserId = userId;
+    }
+
+    public int getValue() {
+        return mListening ? mObservedValue : getValueFromProvider();
+    }
+
+    /**
+     * Set the value of the observed setting.
+     *
+     * @param value The new value for the setting.
+     */
+    public void setValue(int value) {
+        mSettingsProxy.putIntForUser(mSettingName, value, mUserId);
+    }
+
+    private int getValueFromProvider() {
+        return mSettingsProxy.getIntForUser(mSettingName, mDefaultValue, mUserId);
+    }
+
+    @Override
+    public void setListening(boolean listening) {
+        if (listening == mListening) return;
+        mListening = listening;
+        if (listening) {
+            mObservedValue = getValueFromProvider();
+            mSettingsProxy.registerContentObserverForUser(
+                    mSettingsProxy.getUriFor(mSettingName), false, this, mUserId);
+        } else {
+            mSettingsProxy.unregisterContentObserver(this);
+            mObservedValue = mDefaultValue;
+        }
+    }
+
+    @Override
+    public void onChange(boolean selfChange) {
+        final int value = getValueFromProvider();
+        final boolean changed = value != mObservedValue;
+        mObservedValue = value;
+        handleValueChanged(value, changed);
+    }
+
+    /**
+     * Set user handle for which to observe the setting.
+     */
+    public void setUserId(int userId) {
+        mUserId = userId;
+        if (mListening) {
+            setListening(false);
+            setListening(true);
+        }
+    }
+
+    public int getCurrentUser() {
+        return mUserId;
+    }
+
+    public String getKey() {
+        return mSettingName;
+    }
+
+    public boolean isListening() {
+        return mListening;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 2469a98..78f2da5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -17,6 +17,7 @@
 
 import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT;
 
+import android.app.ActivityManager;
 import android.app.compat.CompatChanges;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -35,6 +36,7 @@
 import android.service.quicksettings.IQSService;
 import android.service.quicksettings.IQSTileService;
 import android.service.quicksettings.TileService;
+import android.text.format.DateUtils;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -81,7 +83,8 @@
 
     // Bind retry control.
     private static final int MAX_BIND_RETRIES = 5;
-    private static final int DEFAULT_BIND_RETRY_DELAY = 1000;
+    private static final long DEFAULT_BIND_RETRY_DELAY = 5 * DateUtils.SECOND_IN_MILLIS;
+    private static final long LOW_MEMORY_BIND_RETRY_DELAY = 20 * DateUtils.SECOND_IN_MILLIS;
 
     // Shared prefs that hold tile lifecycle info.
     private static final String TILES = "tiles_prefs";
@@ -94,6 +97,7 @@
     private final IBinder mToken = new Binder();
     private final PackageManagerAdapter mPackageManagerAdapter;
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final ActivityManager mActivityManager;
 
     private Set<Integer> mQueuedMessages = new ArraySet<>();
     @Nullable
@@ -102,7 +106,8 @@
     private IBinder mClickBinder;
 
     private int mBindTryCount;
-    private int mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY;
+    private long mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY;
+    private AtomicBoolean isDeathRebindScheduled = new AtomicBoolean(false);
     private AtomicBoolean mBound = new AtomicBoolean(false);
     private AtomicBoolean mPackageReceiverRegistered = new AtomicBoolean(false);
     private AtomicBoolean mUserReceiverRegistered = new AtomicBoolean(false);
@@ -115,7 +120,7 @@
     @AssistedInject
     TileLifecycleManager(@Main Handler handler, Context context, IQSService service,
             PackageManagerAdapter packageManagerAdapter, BroadcastDispatcher broadcastDispatcher,
-            @Assisted Intent intent, @Assisted UserHandle user,
+            @Assisted Intent intent, @Assisted UserHandle user, ActivityManager activityManager,
             @Background DelayableExecutor executor) {
         mContext = context;
         mHandler = handler;
@@ -126,6 +131,7 @@
         mExecutor = executor;
         mPackageManagerAdapter = packageManagerAdapter;
         mBroadcastDispatcher = broadcastDispatcher;
+        mActivityManager = activityManager;
         if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser);
     }
 
@@ -152,10 +158,6 @@
         }
     }
 
-    public void setBindRetryDelay(int delayMs) {
-        mBindRetryDelay = delayMs;
-    }
-
     public boolean isActiveTile() {
         try {
             ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
@@ -250,19 +252,15 @@
 
     private boolean bindServices() {
         String packageName = mIntent.getComponent().getPackageName();
+        int flags = Context.BIND_AUTO_CREATE
+                | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
+                | Context.BIND_WAIVE_PRIORITY;
         if (CompatChanges.isChangeEnabled(START_ACTIVITY_NEEDS_PENDING_INTENT, packageName,
                 mUser)) {
-            return mContext.bindServiceAsUser(mIntent, this,
-                    Context.BIND_AUTO_CREATE
-                            | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
-                            | Context.BIND_WAIVE_PRIORITY,
-                    mUser);
+            return mContext.bindServiceAsUser(mIntent, this, flags, mUser);
         }
         return mContext.bindServiceAsUser(mIntent, this,
-                Context.BIND_AUTO_CREATE
-                        | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
-                        | Context.BIND_WAIVE_PRIORITY,
+                flags | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
                 mUser);
     }
 
@@ -352,10 +350,34 @@
         if (!mBound.get()) return;
         if (DEBUG) Log.d(TAG, "handleDeath");
         if (checkComponentState()) {
-            mExecutor.executeDelayed(() -> setBindService(true), mBindRetryDelay);
+            if (isDeathRebindScheduled.compareAndSet(false, true)) {
+                mExecutor.executeDelayed(() -> {
+                    setBindService(true);
+                    isDeathRebindScheduled.set(false);
+                }, getRebindDelay());
+            }
         }
     }
 
+    /**
+     * @return the delay to automatically rebind after a service died. It provides a longer delay if
+     * the device is a low memory state because the service is likely to get killed again by the
+     * system. In this case we want to rebind later and not to cause a loop of a frequent rebinds.
+     */
+    private long getRebindDelay() {
+        final ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
+        mActivityManager.getMemoryInfo(info);
+
+        final long delay;
+        if (info.lowMemory) {
+            delay = LOW_MEMORY_BIND_RETRY_DELAY;
+        } else {
+            delay = mBindRetryDelay;
+        }
+        Log.i(TAG, "Rebinding with a delay=" + delay);
+        return delay;
+    }
+
     private boolean checkComponentState() {
         if (!isPackageAvailable() || !isComponentAvailable()) {
             startPackageListening();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index 941a9d6..3ee4a1b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -75,13 +75,12 @@
     private boolean mStarted = false;
 
     TileServiceManager(TileServices tileServices, Handler handler, ComponentName component,
-            BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
-            CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) {
+            UserTracker userTracker, TileLifecycleManager.Factory tileLifecycleManagerFactory,
+            CustomTileAddedRepository customTileAddedRepository) {
         this(tileServices, handler, userTracker, customTileAddedRepository,
-                new TileLifecycleManager(handler, tileServices.getContext(), tileServices,
-                        new PackageManagerAdapter(tileServices.getContext()), broadcastDispatcher,
+                tileLifecycleManagerFactory.create(
                         new Intent(TileService.ACTION_QS_TILE).setComponent(component),
-                        userTracker.getUserHandle(), executor));
+                        userTracker.getUserHandle()));
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index acee8e9..c3744df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -81,6 +81,7 @@
     private final UserTracker mUserTracker;
     private final StatusBarIconController mStatusBarIconController;
     private final PanelInteractor mPanelInteractor;
+    private final TileLifecycleManager.Factory mTileLifecycleManagerFactory;
     private final CustomTileAddedRepository mCustomTileAddedRepository;
     private final DelayableExecutor mBackgroundExecutor;
 
@@ -96,6 +97,7 @@
             CommandQueue commandQueue,
             StatusBarIconController statusBarIconController,
             PanelInteractor panelInteractor,
+            TileLifecycleManager.Factory tileLifecycleManagerFactory,
             CustomTileAddedRepository customTileAddedRepository,
             @Background DelayableExecutor backgroundExecutor) {
         mHost = host;
@@ -109,6 +111,7 @@
         mStatusBarIconController = statusBarIconController;
         mCommandQueue.addCallback(mRequestListeningCallback);
         mPanelInteractor = panelInteractor;
+        mTileLifecycleManagerFactory = tileLifecycleManagerFactory;
         mCustomTileAddedRepository = customTileAddedRepository;
         mBackgroundExecutor = backgroundExecutor;
     }
@@ -137,8 +140,8 @@
 
     protected TileServiceManager onCreateTileService(ComponentName component,
             BroadcastDispatcher broadcastDispatcher) {
-        return new TileServiceManager(this, mHandlerProvider.get(), component,
-                broadcastDispatcher, mUserTracker, mCustomTileAddedRepository, mBackgroundExecutor);
+        return new TileServiceManager(this, mHandlerProvider.get(), component, mUserTracker,
+                mTileLifecycleManagerFactory, mCustomTileAddedRepository);
     }
 
     public void freeService(CustomTileInterface tile, TileServiceManager service) {
@@ -323,7 +326,7 @@
                 if (info.applicationInfo.isSystemApp()) {
                     final StatusBarIcon statusIcon = icon != null
                             ? new StatusBarIcon(userHandle, packageName, icon, 0, 0,
-                                    contentDescription)
+                            contentDescription)
                             : null;
                     final String slot = getStatusBarIconSlotName(componentName);
                     mMainHandler.post(new Runnable() {
@@ -356,11 +359,11 @@
         synchronized (mServices) {
             mTokenMap.forEach((iBinder, customTile) ->
                     sb.append(iBinder.toString())
-                    .append(":")
-                    .append(customTile.getComponent().flattenToShortString())
-                    .append(":")
-                    .append(customTile.getUser())
-                    .append(","));
+                            .append(":")
+                            .append(customTile.getComponent().flattenToShortString())
+                            .append(":")
+                            .append(customTile.getUser())
+                            .append(","));
         }
         sb.append("]");
         return sb.toString();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index 64fa33c..eff3e76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
 import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
 import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
+import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
 import com.android.systemui.res.R
 import com.android.systemui.util.icuMessageFormat
 import javax.inject.Inject
@@ -47,17 +48,35 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 
+private const val TAG = "FooterActionsViewModel"
+
 /** A ViewModel for the footer actions. */
 class FooterActionsViewModel(
-    @Application appContext: Context,
-    private val footerActionsInteractor: FooterActionsInteractor,
-    private val falsingManager: FalsingManager,
-    private val globalActionsDialogLite: GlobalActionsDialogLite,
-    showPowerButton: Boolean,
-) {
-    /** The context themed with the Quick Settings colors. */
-    private val context = ContextThemeWrapper(appContext, R.style.Theme_SystemUI_QuickSettings)
+    /** The model for the security button. */
+    val security: Flow<FooterActionsSecurityButtonViewModel?>,
 
+    /** The model for the foreground services button. */
+    val foregroundServices: Flow<FooterActionsForegroundServicesButtonViewModel?>,
+
+    /** The model for the user switcher button. */
+    val userSwitcher: Flow<FooterActionsButtonViewModel?>,
+
+    /** The model for the settings button. */
+    val settings: FooterActionsButtonViewModel,
+
+    /** The model for the power button. */
+    val power: FooterActionsButtonViewModel?,
+
+    /**
+     * Observe the device monitoring dialog requests and show the dialog accordingly. This function
+     * will suspend indefinitely and will need to be cancelled to stop observing.
+     *
+     * Important: [quickSettingsContext] must be the [Context] associated to the
+     * [Quick Settings fragment][com.android.systemui.qs.QSFragmentLegacy], and the call to this
+     * function must be cancelled when that fragment is destroyed.
+     */
+    val observeDeviceMonitoringDialogRequests: suspend (quickSettingsContext: Context) -> Unit,
+) {
     /**
      * Whether the UI rendering this ViewModel should be visible. Note that even when this is false,
      * the UI should still participate to the layout it is included in (i.e. in the View world it
@@ -74,107 +93,6 @@
     private val _backgroundAlpha = MutableStateFlow(1f)
     val backgroundAlpha: StateFlow<Float> = _backgroundAlpha.asStateFlow()
 
-    /** The model for the security button. */
-    val security: Flow<FooterActionsSecurityButtonViewModel?> =
-        footerActionsInteractor.securityButtonConfig
-            .map { config ->
-                val (icon, text, isClickable) = config ?: return@map null
-                FooterActionsSecurityButtonViewModel(
-                    icon,
-                    text,
-                    if (isClickable) this::onSecurityButtonClicked else null,
-                )
-            }
-            .distinctUntilChanged()
-
-    /** The model for the foreground services button. */
-    val foregroundServices: Flow<FooterActionsForegroundServicesButtonViewModel?> =
-        combine(
-                footerActionsInteractor.foregroundServicesCount,
-                footerActionsInteractor.hasNewForegroundServices,
-                security,
-            ) { foregroundServicesCount, hasNewChanges, securityModel ->
-                if (foregroundServicesCount <= 0) {
-                    return@combine null
-                }
-
-                val text =
-                    icuMessageFormat(
-                        context.resources,
-                        R.string.fgs_manager_footer_label,
-                        foregroundServicesCount,
-                    )
-                FooterActionsForegroundServicesButtonViewModel(
-                    foregroundServicesCount,
-                    text = text,
-                    displayText = securityModel == null,
-                    hasNewChanges = hasNewChanges,
-                    this::onForegroundServiceButtonClicked,
-                )
-            }
-            .distinctUntilChanged()
-
-    /** The model for the user switcher button. */
-    val userSwitcher: Flow<FooterActionsButtonViewModel?> =
-        footerActionsInteractor.userSwitcherStatus
-            .map { userSwitcherStatus ->
-                when (userSwitcherStatus) {
-                    UserSwitcherStatusModel.Disabled -> null
-                    is UserSwitcherStatusModel.Enabled -> {
-                        if (userSwitcherStatus.currentUserImage == null) {
-                            Log.e(
-                                TAG,
-                                "Skipped the addition of user switcher button because " +
-                                    "currentUserImage is missing",
-                            )
-                            return@map null
-                        }
-
-                        userSwitcherButton(userSwitcherStatus)
-                    }
-                }
-            }
-            .distinctUntilChanged()
-
-    /** The model for the settings button. */
-    val settings: FooterActionsButtonViewModel =
-        FooterActionsButtonViewModel(
-            id = R.id.settings_button_container,
-            Icon.Resource(
-                R.drawable.ic_settings,
-                ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
-            ),
-            iconTint =
-                Utils.getColorAttrDefaultColor(
-                    context,
-                    R.attr.onShadeInactiveVariant,
-                ),
-            backgroundColor = R.attr.shadeInactive,
-            this::onSettingsButtonClicked,
-        )
-
-    /** The model for the power button. */
-    val power: FooterActionsButtonViewModel? =
-        if (showPowerButton) {
-            FooterActionsButtonViewModel(
-                id = R.id.pm_lite,
-                Icon.Resource(
-                    android.R.drawable.ic_lock_power_off,
-                    ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu)
-                ),
-                iconTint =
-                    Utils.getColorAttrDefaultColor(
-                        context,
-                        R.attr.onShadeActive,
-                    ),
-                backgroundColor = R.attr.shadeActive,
-                this::onPowerButtonClicked,
-            )
-        } else {
-            null
-        }
-
-    /** Called when the visibility of the UI rendering this model should be changed. */
     fun onVisibilityChangeRequested(visible: Boolean) {
         _isVisible.value = visible
     }
@@ -195,89 +113,6 @@
         }
     }
 
-    /**
-     * Observe the device monitoring dialog requests and show the dialog accordingly. This function
-     * will suspend indefinitely and will need to be cancelled to stop observing.
-     *
-     * Important: [quickSettingsContext] must be the [Context] associated to the
-     * [Quick Settings fragment][com.android.systemui.qs.QSFragmentLegacy], and the call to this
-     * function must be cancelled when that fragment is destroyed.
-     */
-    suspend fun observeDeviceMonitoringDialogRequests(quickSettingsContext: Context) {
-        footerActionsInteractor.deviceMonitoringDialogRequests.collect {
-            footerActionsInteractor.showDeviceMonitoringDialog(
-                quickSettingsContext,
-                expandable = null,
-            )
-        }
-    }
-
-    private fun onSecurityButtonClicked(quickSettingsContext: Context, expandable: Expandable) {
-        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-            return
-        }
-
-        footerActionsInteractor.showDeviceMonitoringDialog(quickSettingsContext, expandable)
-    }
-
-    private fun onForegroundServiceButtonClicked(expandable: Expandable) {
-        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-            return
-        }
-
-        footerActionsInteractor.showForegroundServicesDialog(expandable)
-    }
-
-    private fun onUserSwitcherClicked(expandable: Expandable) {
-        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-            return
-        }
-
-        footerActionsInteractor.showUserSwitcher(expandable)
-    }
-
-    private fun onSettingsButtonClicked(expandable: Expandable) {
-        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-            return
-        }
-
-        footerActionsInteractor.showSettings(expandable)
-    }
-
-    private fun onPowerButtonClicked(expandable: Expandable) {
-        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-            return
-        }
-
-        footerActionsInteractor.showPowerMenuDialog(globalActionsDialogLite, expandable)
-    }
-
-    private fun userSwitcherButton(
-        status: UserSwitcherStatusModel.Enabled
-    ): FooterActionsButtonViewModel {
-        val icon = status.currentUserImage!!
-
-        return FooterActionsButtonViewModel(
-            id = R.id.multi_user_switch,
-            icon =
-                Icon.Loaded(
-                    icon,
-                    ContentDescription.Loaded(
-                        userSwitcherContentDescription(status.currentUserName)
-                    ),
-                ),
-            iconTint = null,
-            backgroundColor = R.attr.shadeInactive,
-            onClick = this::onUserSwitcherClicked,
-        )
-    }
-
-    private fun userSwitcherContentDescription(currentUser: String?): String? {
-        return currentUser?.let { user ->
-            context.getString(R.string.accessibility_quick_settings_user, user)
-        }
-    }
-
     @SysUISingleton
     class Factory
     @Inject
@@ -315,8 +150,237 @@
             )
         }
     }
+}
 
-    companion object {
-        private const val TAG = "FooterActionsViewModel"
+fun FooterActionsViewModel(
+    @Application appContext: Context,
+    footerActionsInteractor: FooterActionsInteractor,
+    falsingManager: FalsingManager,
+    globalActionsDialogLite: GlobalActionsDialogLite,
+    showPowerButton: Boolean,
+): FooterActionsViewModel {
+    suspend fun observeDeviceMonitoringDialogRequests(quickSettingsContext: Context) {
+        footerActionsInteractor.deviceMonitoringDialogRequests.collect {
+            footerActionsInteractor.showDeviceMonitoringDialog(
+                quickSettingsContext,
+                expandable = null,
+            )
+        }
     }
+
+    fun onSecurityButtonClicked(quickSettingsContext: Context, expandable: Expandable) {
+        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+            return
+        }
+
+        footerActionsInteractor.showDeviceMonitoringDialog(quickSettingsContext, expandable)
+    }
+
+    fun onForegroundServiceButtonClicked(expandable: Expandable) {
+        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+            return
+        }
+
+        footerActionsInteractor.showForegroundServicesDialog(expandable)
+    }
+
+    fun onUserSwitcherClicked(expandable: Expandable) {
+        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+            return
+        }
+
+        footerActionsInteractor.showUserSwitcher(expandable)
+    }
+
+    fun onSettingsButtonClicked(expandable: Expandable) {
+        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+            return
+        }
+
+        footerActionsInteractor.showSettings(expandable)
+    }
+
+    fun onPowerButtonClicked(expandable: Expandable) {
+        if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+            return
+        }
+
+        footerActionsInteractor.showPowerMenuDialog(globalActionsDialogLite, expandable)
+    }
+
+    val qsThemedContext = ContextThemeWrapper(appContext, R.style.Theme_SystemUI_QuickSettings)
+
+    val security =
+        footerActionsInteractor.securityButtonConfig
+            .map { config ->
+                config?.let { securityButtonViewModel(it, ::onSecurityButtonClicked) }
+            }
+            .distinctUntilChanged()
+
+    val foregroundServices =
+        combine(
+                footerActionsInteractor.foregroundServicesCount,
+                footerActionsInteractor.hasNewForegroundServices,
+                security,
+            ) { foregroundServicesCount, hasNewChanges, securityModel ->
+                if (foregroundServicesCount <= 0) {
+                    return@combine null
+                }
+
+                foregroundServicesButtonViewModel(
+                    qsThemedContext,
+                    foregroundServicesCount,
+                    securityModel,
+                    hasNewChanges,
+                    ::onForegroundServiceButtonClicked,
+                )
+            }
+            .distinctUntilChanged()
+
+    val userSwitcher =
+        footerActionsInteractor.userSwitcherStatus
+            .map { userSwitcherStatus ->
+                when (userSwitcherStatus) {
+                    UserSwitcherStatusModel.Disabled -> null
+                    is UserSwitcherStatusModel.Enabled -> {
+                        if (userSwitcherStatus.currentUserImage == null) {
+                            Log.e(
+                                TAG,
+                                "Skipped the addition of user switcher button because " +
+                                    "currentUserImage is missing",
+                            )
+                            return@map null
+                        }
+
+                        userSwitcherButtonViewModel(
+                            qsThemedContext,
+                            userSwitcherStatus,
+                            ::onUserSwitcherClicked
+                        )
+                    }
+                }
+            }
+            .distinctUntilChanged()
+
+    val settings = settingsButtonViewModel(qsThemedContext, ::onSettingsButtonClicked)
+    val power =
+        if (showPowerButton) {
+            powerButtonViewModel(qsThemedContext, ::onPowerButtonClicked)
+        } else {
+            null
+        }
+
+    return FooterActionsViewModel(
+        security = security,
+        foregroundServices = foregroundServices,
+        userSwitcher = userSwitcher,
+        settings = settings,
+        power = power,
+        observeDeviceMonitoringDialogRequests = ::observeDeviceMonitoringDialogRequests,
+    )
+}
+
+fun securityButtonViewModel(
+    config: SecurityButtonConfig,
+    onSecurityButtonClicked: (Context, Expandable) -> Unit,
+): FooterActionsSecurityButtonViewModel {
+    val (icon, text, isClickable) = config
+    return FooterActionsSecurityButtonViewModel(
+        icon,
+        text,
+        if (isClickable) onSecurityButtonClicked else null,
+    )
+}
+
+fun foregroundServicesButtonViewModel(
+    qsThemedContext: Context,
+    foregroundServicesCount: Int,
+    securityModel: FooterActionsSecurityButtonViewModel?,
+    hasNewChanges: Boolean,
+    onForegroundServiceButtonClicked: (Expandable) -> Unit,
+): FooterActionsForegroundServicesButtonViewModel {
+    val text =
+        icuMessageFormat(
+            qsThemedContext.resources,
+            R.string.fgs_manager_footer_label,
+            foregroundServicesCount,
+        )
+
+    return FooterActionsForegroundServicesButtonViewModel(
+        foregroundServicesCount,
+        text = text,
+        displayText = securityModel == null,
+        hasNewChanges = hasNewChanges,
+        onForegroundServiceButtonClicked,
+    )
+}
+
+fun userSwitcherButtonViewModel(
+    qsThemedContext: Context,
+    status: UserSwitcherStatusModel.Enabled,
+    onUserSwitcherClicked: (Expandable) -> Unit,
+): FooterActionsButtonViewModel {
+    val icon = status.currentUserImage!!
+    return FooterActionsButtonViewModel(
+        id = R.id.multi_user_switch,
+        icon =
+            Icon.Loaded(
+                icon,
+                ContentDescription.Loaded(
+                    userSwitcherContentDescription(qsThemedContext, status.currentUserName)
+                ),
+            ),
+        iconTint = null,
+        backgroundColor = R.attr.shadeInactive,
+        onClick = onUserSwitcherClicked,
+    )
+}
+
+private fun userSwitcherContentDescription(
+    qsThemedContext: Context,
+    currentUser: String?
+): String? {
+    return currentUser?.let { user ->
+        qsThemedContext.getString(R.string.accessibility_quick_settings_user, user)
+    }
+}
+
+fun settingsButtonViewModel(
+    qsThemedContext: Context,
+    onSettingsButtonClicked: (Expandable) -> Unit,
+): FooterActionsButtonViewModel {
+    return FooterActionsButtonViewModel(
+        id = R.id.settings_button_container,
+        Icon.Resource(
+            R.drawable.ic_settings,
+            ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
+        ),
+        iconTint =
+            Utils.getColorAttrDefaultColor(
+                qsThemedContext,
+                R.attr.onShadeInactiveVariant,
+            ),
+        backgroundColor = R.attr.shadeInactive,
+        onSettingsButtonClicked,
+    )
+}
+
+fun powerButtonViewModel(
+    qsThemedContext: Context,
+    onPowerButtonClicked: (Expandable) -> Unit,
+): FooterActionsButtonViewModel {
+    return FooterActionsButtonViewModel(
+        id = R.id.pm_lite,
+        Icon.Resource(
+            android.R.drawable.ic_lock_power_off,
+            ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu)
+        ),
+        iconTint =
+            Utils.getColorAttrDefaultColor(
+                qsThemedContext,
+                R.attr.onShadeActive,
+            ),
+        backgroundColor = R.attr.shadeActive,
+        onPowerButtonClicked,
+    )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 5a9c0a1..f8e0159 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -23,7 +23,10 @@
 import android.content.res.ColorStateList
 import android.content.res.Configuration
 import android.content.res.Resources.ID_NULL
+import android.graphics.Color
+import android.graphics.PorterDuff
 import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
 import android.graphics.drawable.RippleDrawable
 import android.os.Trace
 import android.service.quicksettings.Tile
@@ -44,7 +47,6 @@
 import androidx.annotation.VisibleForTesting
 import com.android.settingslib.Utils
 import com.android.systemui.FontSizeUtils
-import com.android.systemui.res.R
 import com.android.systemui.animation.LaunchableView
 import com.android.systemui.animation.LaunchableViewDelegate
 import com.android.systemui.plugins.qs.QSIconView
@@ -53,6 +55,7 @@
 import com.android.systemui.plugins.qs.QSTileView
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH
+import com.android.systemui.res.R
 import java.util.Objects
 
 private const val TAG = "QSTileViewImpl"
@@ -67,6 +70,7 @@
         private const val LABEL_NAME = "label"
         private const val SECONDARY_LABEL_NAME = "secondaryLabel"
         private const val CHEVRON_NAME = "chevron"
+        private const val OVERLAY_NAME = "overlay"
         const val UNAVAILABLE_ALPHA = 0.3f
         @VisibleForTesting
         internal const val TILE_STATE_RES_PREFIX = "tile_states_"
@@ -97,6 +101,13 @@
     private val colorInactive = Utils.getColorAttrDefaultColor(context, R.attr.shadeInactive)
     private val colorUnavailable = Utils.getColorAttrDefaultColor(context, R.attr.shadeDisabled)
 
+    private val overlayColorActive = Utils.applyAlpha(
+        /* alpha= */ 0.11f,
+        Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive))
+    private val overlayColorInactive = Utils.applyAlpha(
+        /* alpha= */ 0.08f,
+        Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive))
+
     private val colorLabelActive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive)
     private val colorLabelInactive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive)
     private val colorLabelUnavailable =
@@ -123,8 +134,13 @@
     protected var showRippleEffect = true
 
     private lateinit var ripple: RippleDrawable
-    private lateinit var colorBackgroundDrawable: Drawable
-    private var paintColor: Int = 0
+    private lateinit var backgroundDrawable: LayerDrawable
+    private lateinit var backgroundBaseDrawable: Drawable
+    private lateinit var backgroundOverlayDrawable: Drawable
+
+    private var backgroundColor: Int = 0
+    private var backgroundOverlayColor: Int = 0
+
     private val singleAnimator: ValueAnimator = ValueAnimator().apply {
         setDuration(QS_ANIM_LENGTH)
         addUpdateListener { animation ->
@@ -134,7 +150,8 @@
                 animation.getAnimatedValue(BACKGROUND_NAME) as Int,
                 animation.getAnimatedValue(LABEL_NAME) as Int,
                 animation.getAnimatedValue(SECONDARY_LABEL_NAME) as Int,
-                animation.getAnimatedValue(CHEVRON_NAME) as Int
+                animation.getAnimatedValue(CHEVRON_NAME) as Int,
+                animation.getAnimatedValue(OVERLAY_NAME) as Int,
             )
         }
     }
@@ -263,7 +280,12 @@
 
     fun createTileBackground(): Drawable {
         ripple = mContext.getDrawable(R.drawable.qs_tile_background) as RippleDrawable
-        colorBackgroundDrawable = ripple.findDrawableByLayerId(R.id.background)
+        backgroundDrawable = ripple.findDrawableByLayerId(R.id.background) as LayerDrawable
+        backgroundBaseDrawable =
+            backgroundDrawable.findDrawableByLayerId(R.id.qs_tile_background_base)
+        backgroundOverlayDrawable =
+            backgroundDrawable.findDrawableByLayerId(R.id.qs_tile_background_overlay)
+        backgroundOverlayDrawable.mutate().setTintMode(PorterDuff.Mode.SRC)
         return ripple
     }
 
@@ -343,10 +365,10 @@
             ripple.also {
                 // In case that the colorBackgroundDrawable was used as the background, make sure
                 // it has the correct callback instead of null
-                colorBackgroundDrawable.callback = it
+                backgroundDrawable.callback = it
             }
         } else {
-            colorBackgroundDrawable
+            backgroundDrawable
         }
     }
 
@@ -512,7 +534,7 @@
                 singleAnimator.setValues(
                         colorValuesHolder(
                                 BACKGROUND_NAME,
-                                paintColor,
+                                backgroundColor,
                                 getBackgroundColorForState(state.state, state.disabledByPolicy)
                         ),
                         colorValuesHolder(
@@ -529,6 +551,11 @@
                                 CHEVRON_NAME,
                                 chevronView.imageTintList?.defaultColor ?: 0,
                                 getChevronColorForState(state.state, state.disabledByPolicy)
+                        ),
+                        colorValuesHolder(
+                                OVERLAY_NAME,
+                                backgroundOverlayColor,
+                                getOverlayColorForState(state.state)
                         )
                     )
                 singleAnimator.start()
@@ -537,7 +564,8 @@
                     getBackgroundColorForState(state.state, state.disabledByPolicy),
                     getLabelColorForState(state.state, state.disabledByPolicy),
                     getSecondaryLabelColorForState(state.state, state.disabledByPolicy),
-                    getChevronColorForState(state.state, state.disabledByPolicy)
+                    getChevronColorForState(state.state, state.disabledByPolicy),
+                    getOverlayColorForState(state.state)
                 )
             }
         }
@@ -555,17 +583,19 @@
         backgroundColor: Int,
         labelColor: Int,
         secondaryLabelColor: Int,
-        chevronColor: Int
+        chevronColor: Int,
+        overlayColor: Int,
     ) {
         setColor(backgroundColor)
         setLabelColor(labelColor)
         setSecondaryLabelColor(secondaryLabelColor)
         setChevronColor(chevronColor)
+        setOverlayColor(overlayColor)
     }
 
     private fun setColor(color: Int) {
-        colorBackgroundDrawable.mutate().setTint(color)
-        paintColor = color
+        backgroundBaseDrawable.mutate().setTint(color)
+        backgroundColor = color
     }
 
     private fun setLabelColor(color: Int) {
@@ -580,6 +610,11 @@
         chevronView.imageTintList = ColorStateList.valueOf(color)
     }
 
+    private fun setOverlayColor(overlayColor: Int) {
+        backgroundOverlayDrawable.setTint(overlayColor)
+        backgroundOverlayColor = overlayColor
+    }
+
     private fun loadSideViewDrawableIfNecessary(state: QSTile.State) {
         if (state.sideViewCustomDrawable != null) {
             customDrawableView.setImageDrawable(state.sideViewCustomDrawable)
@@ -654,9 +689,17 @@
     private fun getChevronColorForState(state: Int, disabledByPolicy: Boolean = false): Int =
             getSecondaryLabelColorForState(state, disabledByPolicy)
 
+    private fun getOverlayColorForState(state: Int): Int {
+        return when (state) {
+            Tile.STATE_ACTIVE -> overlayColorActive
+            Tile.STATE_INACTIVE -> overlayColorInactive
+            else -> Color.TRANSPARENT
+        }
+    }
+
     @VisibleForTesting
     internal fun getCurrentColors(): List<Int> = listOf(
-            paintColor,
+            backgroundColor,
             label.currentTextColor,
             secondaryLabel.currentTextColor,
             chevronView.imageTintList?.defaultColor ?: 0
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index fb71cef..17251c3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -36,7 +36,6 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -49,6 +48,7 @@
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.GlobalSettings;
 
@@ -56,8 +56,6 @@
 
 import javax.inject.Inject;
 
-
-
 /** Quick settings tile: Airplane mode **/
 public class AirplaneModeTile extends QSTileImpl<BooleanState> {
 
@@ -90,8 +88,7 @@
         mBroadcastDispatcher = broadcastDispatcher;
         mLazyConnectivityManager = lazyConnectivityManager;
 
-        mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON,
-                userTracker.getUserId()) {
+        mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
                 // mHandler is the background handler so calling this is OK
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index 18d472b..426aa55 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -29,7 +29,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -38,9 +37,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 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.policy.BatteryController;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -53,7 +53,7 @@
 
     private final BatteryController mBatteryController;
     @VisibleForTesting
-    protected final SettingObserver mSetting;
+    protected final UserSettingObserver mSetting;
 
     private int mLevel;
     private boolean mPowerSave;
@@ -79,7 +79,7 @@
         mBatteryController = batteryController;
         mBatteryController.observe(getLifecycle(), this);
         int currentUser = host.getUserContext().getUserId();
-        mSetting = new SettingObserver(
+        mSetting = new UserSettingObserver(
                 secureSettings,
                 mHandler,
                 Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index d862f56..18d2f30 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -39,7 +39,6 @@
 import com.android.settingslib.Utils;
 import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
@@ -53,6 +52,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogViewModel;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.BluetoothController;
 
 import java.util.List;
@@ -198,6 +198,7 @@
         }
 
         state.expandedAccessibilityClassName = Switch.class.getName();
+        state.forceExpandIcon = mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG);
     }
 
     /**
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/qs/tiles/ColorCorrectionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
index ee57b2b..c8adbfc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
@@ -28,8 +28,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
-import com.android.systemui.res.R.drawable;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -38,9 +36,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -51,8 +50,8 @@
 
     public static final String TILE_SPEC = "color_correction";
 
-    private final Icon mIcon = ResourceIcon.get(drawable.ic_qs_color_correction);
-    private final SettingObserver mSetting;
+    private final Icon mIcon = ResourceIcon.get(R.drawable.ic_qs_color_correction);
+    private final UserSettingObserver mSetting;
 
     @Inject
     public ColorCorrectionTile(
@@ -71,7 +70,7 @@
         super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
 
-        mSetting = new SettingObserver(secureSettings, mHandler,
+        mSetting = new UserSettingObserver(secureSettings, mHandler,
                 Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 993ada6..c34a584 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -29,8 +29,6 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
-import com.android.systemui.res.R.drawable;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -39,9 +37,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -51,7 +50,7 @@
 public class ColorInversionTile extends QSTileImpl<BooleanState> {
 
     public static final String TILE_SPEC = "inversion";
-    private final SettingObserver mSetting;
+    private final UserSettingObserver mSetting;
 
     @Inject
     public ColorInversionTile(
@@ -70,7 +69,7 @@
         super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
 
-        mSetting = new SettingObserver(secureSettings, mHandler,
+        mSetting = new UserSettingObserver(secureSettings, mHandler,
                 Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
@@ -126,8 +125,8 @@
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
         state.label = mContext.getString(R.string.quick_settings_inversion_label);
         state.icon = ResourceIcon.get(state.value
-                ? drawable.qs_invert_colors_icon_on
-                : drawable.qs_invert_colors_icon_off);
+                ? R.drawable.qs_invert_colors_icon_on
+                : R.drawable.qs_invert_colors_icon_off);
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.contentDescription = state.label;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 0617b30..f6518d1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -45,7 +45,6 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.notification.EnableZenModeDialog;
 import com.android.systemui.Prefs;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.DialogCuj;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -56,10 +55,11 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.settings.SecureSettings;
@@ -81,7 +81,7 @@
 
     private final ZenModeController mController;
     private final SharedPreferences mSharedPreferences;
-    private final SettingObserver mSettingZenDuration;
+    private final UserSettingObserver mSettingZenDuration;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
     private final QSZenModeDialogMetricsLogger mQSZenDialogMetricsLogger;
 
@@ -109,7 +109,7 @@
         mSharedPreferences = sharedPreferences;
         mController.observe(getLifecycle(), mZenCallback);
         mDialogLaunchAnimator = dialogLaunchAnimator;
-        mSettingZenDuration = new SettingObserver(secureSettings, mUiHandler,
+        mSettingZenDuration = new UserSettingObserver(secureSettings, mUiHandler,
                 Settings.Secure.ZEN_DURATION, getHost().getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index a08b3fc..4f0a63b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -38,7 +38,6 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -49,9 +48,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -69,8 +69,8 @@
     private final Icon mIconUndocked = ResourceIcon.get(R.drawable.ic_qs_screen_saver_undocked);
     private final IDreamManager mDreamManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
-    private final SettingObserver mEnabledSettingObserver;
-    private final SettingObserver mDreamSettingObserver;
+    private final UserSettingObserver mEnabledSettingObserver;
+    private final UserSettingObserver mDreamSettingObserver;
     private final UserTracker mUserTracker;
     private final boolean mDreamSupported;
     private final boolean mDreamOnlyEnabledForDockUser;
@@ -111,14 +111,14 @@
                 statusBarStateController, activityStarter, qsLogger);
         mDreamManager = dreamManager;
         mBroadcastDispatcher = broadcastDispatcher;
-        mEnabledSettingObserver = new SettingObserver(secureSettings, mHandler,
+        mEnabledSettingObserver = new UserSettingObserver(secureSettings, mHandler,
                 Settings.Secure.SCREENSAVER_ENABLED, userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
                 refreshState();
             }
         };
-        mDreamSettingObserver = new SettingObserver(secureSettings, mHandler,
+        mDreamSettingObserver = new UserSettingObserver(secureSettings, mHandler,
                 Settings.Secure.SCREENSAVER_COMPONENTS, userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
index 78af976..b08e6a5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
@@ -28,7 +28,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -37,9 +36,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.wm.shell.onehanded.OneHanded;
@@ -53,7 +53,7 @@
 
     private final Icon mIcon = ResourceIcon.get(
             com.android.internal.R.drawable.ic_qs_one_handed_mode);
-    private final SettingObserver mSetting;
+    private final UserSettingObserver mSetting;
 
     @Inject
     public OneHandedModeTile(
@@ -70,7 +70,7 @@
             SecureSettings secureSettings) {
         super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
-        mSetting = new SettingObserver(secureSettings, mHandler,
+        mSetting = new UserSettingObserver(secureSettings, mHandler,
                 Settings.Secure.ONE_HANDED_MODE_ENABLED, userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 5a95004..f1d8f9f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -36,7 +36,6 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -45,9 +44,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 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.policy.BatteryController;
 import com.android.systemui.statusbar.policy.RotationLockController;
 import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
@@ -67,7 +67,7 @@
     private final RotationLockController mController;
     private final SensorPrivacyManager mPrivacyManager;
     private final BatteryController mBatteryController;
-    private final SettingObserver mSetting;
+    private final UserSettingObserver mSetting;
     private final boolean mAllowRotationResolver;
 
     @Inject
@@ -93,7 +93,7 @@
         mPrivacyManager = privacyManager;
         mBatteryController = batteryController;
         int currentUser = host.getUserContext().getUserId();
-        mSetting = new SettingObserver(
+        mSetting = new UserSettingObserver(
                 secureSettings,
                 mHandler,
                 Secure.CAMERA_AUTOROTATE,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
index 8ae2dc2..80af76d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
@@ -102,9 +102,10 @@
         showSeeAll: Boolean,
         showPairNewDevice: Boolean
     ) {
-        seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE
-        pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE
-        deviceItemAdapter.refreshDeviceItemList(deviceItem)
+        deviceItemAdapter.refreshDeviceItemList(deviceItem) {
+            seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE
+            pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE
+        }
     }
 
     internal fun onBluetoothStateUpdated(isEnabled: Boolean, subtitleResId: Int) {
@@ -173,8 +174,8 @@
 
         internal fun getItem(position: Int) = asyncListDiffer.currentList[position]
 
-        internal fun refreshDeviceItemList(updated: List<DeviceItem>) {
-            asyncListDiffer.submitList(updated)
+        internal fun refreshDeviceItemList(updated: List<DeviceItem>, callback: () -> Unit) {
+            asyncListDiffer.submitList(updated, callback)
         }
 
         internal inner class DeviceItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
index 97e1783..8e27493 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
@@ -113,7 +113,6 @@
                     .launchIn(this)
 
                 deviceItemInteractor.deviceItemUpdate
-                    .filterNotNull()
                     .onEach {
                         dialog!!.onDeviceItemUpdated(
                             it.take(MAX_DEVICE_ITEM_ENTRY),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
index 14d24f9..e196c6c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
@@ -34,10 +34,10 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.withContext
 
@@ -55,10 +55,10 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) {
 
-    private val mutableDeviceItemUpdate: MutableStateFlow<List<DeviceItem>?> =
-        MutableStateFlow(null)
+    private val mutableDeviceItemUpdate: MutableSharedFlow<List<DeviceItem>> =
+        MutableSharedFlow(extraBufferCapacity = 1)
     internal val deviceItemUpdate
-        get() = mutableDeviceItemUpdate.asStateFlow()
+        get() = mutableDeviceItemUpdate.asSharedFlow()
 
     internal val deviceItemUpdateRequest: SharedFlow<Unit> =
         conflatedCallbackFlow {
@@ -119,16 +119,15 @@
 
     internal suspend fun updateDeviceItems(context: Context) {
         withContext(backgroundDispatcher) {
-            val mostRecentlyConnectedDevices = bluetoothAdapter?.mostRecentlyConnectedDevices
-
-            mutableDeviceItemUpdate.value =
+            mutableDeviceItemUpdate.tryEmit(
                 bluetoothTileDialogRepository.cachedDevices
                     .mapNotNull { cachedDevice ->
                         deviceItemFactoryList
                             .firstOrNull { it.isFilterMatched(cachedDevice, audioManager) }
                             ?.create(context, cachedDevice)
                     }
-                    .sort(displayPriority, mostRecentlyConnectedDevices)
+                    .sort(displayPriority, bluetoothAdapter?.mostRecentlyConnectedDevices)
+            )
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 584456d..91b4d17 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -42,6 +42,7 @@
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -51,7 +52,6 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.launch
-import javax.inject.Inject
 
 /**
  * Hooks up business logic that manipulates the state of the [SceneInteractor] for the system UI
@@ -142,7 +142,7 @@
                                 // When the device becomes unlocked in Lockscreen, go to Gone if
                                 // bypass is enabled.
                                 renderedScenes.contains(SceneKey.Lockscreen) ->
-                                    if (deviceEntryInteractor.isBypassEnabled()) {
+                                    if (deviceEntryInteractor.isBypassEnabled.value) {
                                         SceneKey.Gone to
                                             "device unlocked in Lockscreen scene with bypass"
                                     } else {
@@ -179,36 +179,34 @@
         }
 
         applicationScope.launch {
-            powerInteractor.isAsleep
-                .collect { isAsleep ->
-                    if (isAsleep) {
-                        switchToScene(
-                                targetSceneKey = SceneKey.Lockscreen,
-                                loggingReason = "device is starting to sleep",
-                        )
-                    } else {
-                        val authMethod = authenticationInteractor.getAuthenticationMethod()
-                        val isUnlocked = deviceEntryInteractor.isUnlocked.value
-                        when {
-                            authMethod == AuthenticationMethodModel.None -> {
-                                switchToScene(
-                                        targetSceneKey = SceneKey.Gone,
-                                        loggingReason =
-                                        "device is starting to wake up while auth method is" +
-                                                " none",
-                                )
-                            }
-                            authMethod.isSecure && isUnlocked -> {
-                                switchToScene(
-                                        targetSceneKey = SceneKey.Gone,
-                                        loggingReason =
-                                        "device is starting to wake up while unlocked with a" +
-                                                " secure auth method",
-                                )
-                            }
+            powerInteractor.isAsleep.collect { isAsleep ->
+                if (isAsleep) {
+                    switchToScene(
+                        targetSceneKey = SceneKey.Lockscreen,
+                        loggingReason = "device is starting to sleep",
+                    )
+                } else {
+                    val authMethod = authenticationInteractor.getAuthenticationMethod()
+                    val isUnlocked = deviceEntryInteractor.isUnlocked.value
+                    when {
+                        authMethod == AuthenticationMethodModel.None -> {
+                            switchToScene(
+                                targetSceneKey = SceneKey.Gone,
+                                loggingReason =
+                                    "device is starting to wake up while auth method is" + " none",
+                            )
+                        }
+                        authMethod.isSecure && isUnlocked -> {
+                            switchToScene(
+                                targetSceneKey = SceneKey.Gone,
+                                loggingReason =
+                                    "device is starting to wake up while unlocked with a" +
+                                        " secure auth method",
+                            )
                         }
                     }
                 }
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt
index f704894..3927873 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt
@@ -42,13 +42,6 @@
          * scene, this value will remain true after the pointer is no longer touching the screen and
          * will be true in any transition created to animate back to the original position.
          */
-        val isInitiatedByUserInput: Boolean,
-
-        /**
-         * Whether user input is currently driving the transition. For example, if a user is
-         * dragging a pointer, this emits true. Once they lift their finger, this emits false while
-         * the transition completes/settles.
-         */
-        val isUserInputOngoing: Flow<Boolean>,
+        val isUserInputDriven: Boolean,
     ) : ObservableTransitionState()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
index 4bc93a8..2e45353 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
@@ -61,12 +61,17 @@
     data class Swipe(
         /** The direction of the swipe. */
         val direction: Direction,
+        /**
+         * The edge from which the swipe originated or `null`, if the swipe didn't start close to an
+         * edge.
+         */
+        val fromEdge: Edge? = null,
         /** The number of pointers that were used (for example, one or two fingers). */
         val pointerCount: Int = 1,
     ) : UserAction
 
     /** The user has hit the back button or performed the back navigation gesture. */
-    object Back : UserAction
+    data object Back : UserAction
 }
 
 /** Enumerates all known "cardinal" directions for user actions. */
@@ -76,3 +81,11 @@
     RIGHT,
     DOWN,
 }
+
+/** Enumerates all known edges from which a swipe can start. */
+enum class Edge {
+    LEFT,
+    TOP,
+    RIGHT,
+    BOTTOM,
+}
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/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 9325e18..00d480a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -19,6 +19,7 @@
 import android.app.Activity;
 import android.app.ActivityOptions;
 import android.content.ComponentName;
+import android.content.ContentProvider;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.HardwareRenderer;
@@ -44,10 +45,10 @@
 import com.android.internal.app.ChooserActivity;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.view.OneShotPreDrawListener;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
 import com.android.systemui.screenshot.CropView.CropBoundary;
 import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot;
 import com.android.systemui.settings.UserTracker;
@@ -421,13 +422,15 @@
             Log.e(TAG, "failed to export", e);
             return;
         }
+        Uri exported = ContentProvider.getUriWithoutUserId(result.uri);
+        Log.e(TAG, action + " uri=" + exported);
 
         switch (action) {
             case EDIT:
-                doEdit(result.uri);
+                doEdit(exported);
                 break;
             case SHARE:
-                doShare(result.uri);
+                doShare(exported);
                 break;
             case SAVE:
                 // Nothing more to do
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 5154067..c34fd42 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -20,11 +20,10 @@
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
 import java.util.function.Consumer
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 
 /** Processes a screenshot request sent from [ScreenshotHelper]. */
 interface ScreenshotRequestProcessor {
@@ -36,16 +35,15 @@
     suspend fun process(screenshot: ScreenshotData): ScreenshotData
 }
 
-/**
- * Implementation of [ScreenshotRequestProcessor]
- */
+/** Implementation of [ScreenshotRequestProcessor] */
 @SysUISingleton
-class RequestProcessor @Inject constructor(
-        private val capture: ImageCapture,
-        private val policy: ScreenshotPolicy,
-        private val flags: FeatureFlags,
-        /** For the Java Async version, to invoke the callback. */
-        @Application private val mainScope: CoroutineScope
+class RequestProcessor
+@Inject
+constructor(
+    private val capture: ImageCapture,
+    private val policy: ScreenshotPolicy,
+    /** For the Java Async version, to invoke the callback. */
+    @Application private val mainScope: CoroutineScope
 ) : ScreenshotRequestProcessor {
 
     override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
@@ -67,8 +65,9 @@
             result.userHandle = info.user
 
             if (policy.isManagedProfile(info.user.identifier)) {
-                val image = capture.captureTask(info.taskId)
-                    ?: error("Task snapshot returned a null Bitmap!")
+                val image =
+                    capture.captureTask(info.taskId)
+                        ?: throw RequestProcessorException("Task snapshot returned a null Bitmap!")
 
                 // Provide the task snapshot as the screenshot
                 result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE
@@ -97,3 +96,6 @@
 }
 
 private const val TAG = "RequestProcessor"
+
+/** Exception thrown by [RequestProcessor] if something goes wrong. */
+class RequestProcessorException(message: String) : IllegalStateException(message)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 4b3bd0b..21a08a9 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
@@ -318,7 +325,7 @@
             Context context,
             FeatureFlags flags,
             ScreenshotSmartActions screenshotSmartActions,
-            ScreenshotNotificationsController screenshotNotificationsController,
+            ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
             ScrollCaptureClient scrollCaptureClient,
             UiEventLogger uiEventLogger,
             ImageExporter imageExporter,
@@ -335,10 +342,11 @@
             AssistContentRequester assistContentRequester,
             MessageContainerController messageContainerController,
             Provider<ScreenshotSoundController> screenshotSoundController,
-            @Assisted int displayId
+            @Assisted int displayId,
+            @Assisted boolean showUIOnExternalDisplay
     ) {
         mScreenshotSmartActions = screenshotSmartActions;
-        mNotificationsController = screenshotNotificationsController;
+        mNotificationsController = screenshotNotificationsControllerFactory.create(displayId);
         mScrollCaptureClient = scrollCaptureClient;
         mUiEventLogger = uiEventLogger;
         mImageExporter = imageExporter;
@@ -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/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
deleted file mode 100644
index 4344fd1..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot;
-
-import static android.content.Context.NOTIFICATION_SERVICE;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.os.UserHandle;
-import android.util.DisplayMetrics;
-import android.view.WindowManager;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.systemui.res.R;
-import com.android.systemui.SystemUIApplication;
-import com.android.systemui.util.NotificationChannels;
-
-import javax.inject.Inject;
-
-/**
- * Convenience class to handle showing and hiding notifications while taking a screenshot.
- */
-public class ScreenshotNotificationsController {
-    private static final String TAG = "ScreenshotNotificationManager";
-
-    private final Context mContext;
-    private final Resources mResources;
-    private final NotificationManager mNotificationManager;
-
-    @Inject
-    ScreenshotNotificationsController(Context context, WindowManager windowManager) {
-        mContext = context;
-        mResources = context.getResources();
-        mNotificationManager =
-                (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
-
-        DisplayMetrics displayMetrics = new DisplayMetrics();
-        windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
-    }
-
-    /**
-     * Sends a notification that the screenshot capture has failed.
-     */
-    public void notifyScreenshotError(int msgResId) {
-        Resources res = mContext.getResources();
-        String errorMsg = res.getString(msgResId);
-
-        // Repurpose the existing notification to notify the user of the error
-        Notification.Builder b = new Notification.Builder(mContext, NotificationChannels.ALERTS)
-                .setTicker(res.getString(R.string.screenshot_failed_title))
-                .setContentTitle(res.getString(R.string.screenshot_failed_title))
-                .setContentText(errorMsg)
-                .setSmallIcon(R.drawable.stat_notify_image_error)
-                .setWhen(System.currentTimeMillis())
-                .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
-                .setCategory(Notification.CATEGORY_ERROR)
-                .setAutoCancel(true)
-                .setColor(mContext.getColor(
-                        com.android.internal.R.color.system_notification_accent_color));
-        final DevicePolicyManager dpm =
-                (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
-        final Intent intent =
-                dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
-        if (intent != null) {
-            final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
-                    mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
-            b.setContentIntent(pendingIntent);
-        }
-
-        SystemUIApplication.overrideNotificationAppName(mContext, b, true);
-
-        Notification n = new Notification.BigTextStyle(b)
-                .bigText(errorMsg)
-                .build();
-        mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt
new file mode 100644
index 0000000..d874eb6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.screenshot
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.admin.DevicePolicyManager
+import android.content.Context
+import android.os.UserHandle
+import android.view.Display
+import com.android.internal.R
+import com.android.internal.messages.nano.SystemMessageProto
+import com.android.systemui.SystemUIApplication
+import com.android.systemui.util.NotificationChannels
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Convenience class to handle showing and hiding notifications while taking a screenshot. */
+class ScreenshotNotificationsController
+@AssistedInject
+internal constructor(
+    @Assisted private val displayId: Int,
+    private val context: Context,
+    private val notificationManager: NotificationManager,
+    private val devicePolicyManager: DevicePolicyManager,
+) {
+    private val res = context.resources
+
+    /**
+     * Sends a notification that the screenshot capture has failed.
+     *
+     * Errors for the non-default display are shown in a unique separate notification.
+     */
+    fun notifyScreenshotError(msgResId: Int) {
+        val displayErrorString =
+            if (displayId != Display.DEFAULT_DISPLAY) {
+                " ($externalDisplayString)"
+            } else {
+                ""
+            }
+        val errorMsg = res.getString(msgResId) + displayErrorString
+
+        // Repurpose the existing notification or create a new one
+        val builder =
+            Notification.Builder(context, NotificationChannels.ALERTS)
+                .setTicker(res.getString(com.android.systemui.res.R.string.screenshot_failed_title))
+                .setContentTitle(
+                    res.getString(com.android.systemui.res.R.string.screenshot_failed_title)
+                )
+                .setContentText(errorMsg)
+                .setSmallIcon(com.android.systemui.res.R.drawable.stat_notify_image_error)
+                .setWhen(System.currentTimeMillis())
+                .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
+                .setCategory(Notification.CATEGORY_ERROR)
+                .setAutoCancel(true)
+                .setColor(context.getColor(R.color.system_notification_accent_color))
+        val intent =
+            devicePolicyManager.createAdminSupportIntent(
+                DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE
+            )
+        if (intent != null) {
+            val pendingIntent =
+                PendingIntent.getActivityAsUser(
+                    context,
+                    0,
+                    intent,
+                    PendingIntent.FLAG_IMMUTABLE,
+                    null,
+                    UserHandle.CURRENT
+                )
+            builder.setContentIntent(pendingIntent)
+        }
+        SystemUIApplication.overrideNotificationAppName(context, builder, true)
+        val notification = Notification.BigTextStyle(builder).bigText(errorMsg).build()
+        // A different id for external displays to keep the 2 error notifications separated.
+        val id =
+            if (displayId == Display.DEFAULT_DISPLAY) {
+                SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT
+            } else {
+                SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT_EXTERNAL_DISPLAY
+            }
+        notificationManager.notify(id, notification)
+    }
+
+    private val externalDisplayString: String
+        get() =
+            res.getString(
+                com.android.systemui.res.R.string.screenshot_failed_external_display_indication
+            )
+
+    /** Factory for [ScreenshotNotificationsController]. */
+    @AssistedFactory
+    interface Factory {
+        fun create(displayId: Int = Display.DEFAULT_DISPLAY): ScreenshotNotificationsController
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
index 070fb1e..049799e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
@@ -16,25 +16,29 @@
 
 package com.android.systemui.screenshot;
 
+import android.app.NotificationManager;
+import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.view.WindowManager;
+import android.view.Display;
 
 import com.android.systemui.res.R;
 
 /**
- * Performs a number of miscellaneous, non-system-critical actions
- * after the system has finished booting.
+ * Receives errors related to screenshot.
  */
 public class ScreenshotServiceErrorReceiver extends BroadcastReceiver {
 
     @Override
     public void onReceive(final Context context, Intent intent) {
         // Show a message that we've failed to save the image to disk
-        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        ScreenshotNotificationsController controller =
-                new ScreenshotNotificationsController(context, wm);
+        NotificationManager notificationManager = context.getSystemService(
+                NotificationManager.class);
+        DevicePolicyManager devicePolicyManager = context.getSystemService(
+                DevicePolicyManager.class);
+        ScreenshotNotificationsController controller = new ScreenshotNotificationsController(
+                Display.DEFAULT_DISPLAY, context, notificationManager, devicePolicyManager);
         controller.notifyScreenshotError(R.string.screenshot_failed_to_save_unknown_text);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 03c3f7a..0158284 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -10,6 +10,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.res.R
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
 import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
 import java.util.function.Consumer
 import javax.inject.Inject
@@ -34,7 +36,8 @@
     displayRepository: DisplayRepository,
     @Application private val mainScope: CoroutineScope,
     private val screenshotRequestProcessor: ScreenshotRequestProcessor,
-    private val uiEventLogger: UiEventLogger
+    private val uiEventLogger: UiEventLogger,
+    private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory,
 ) {
 
     private lateinit var displays: StateFlow<Set<Display>>
@@ -44,6 +47,7 @@
         }
 
     private val screenshotControllers = mutableMapOf<Int, ScreenshotController>()
+    private val notificationControllers = mutableMapOf<Int, ScreenshotNotificationsController>()
 
     /**
      * Executes the [ScreenshotRequest].
@@ -58,40 +62,68 @@
     ) {
         val displayIds = getDisplaysToScreenshot(screenshotRequest.type)
         val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
-        screenshotRequest.oneForEachDisplay(displayIds).forEach { screenshotData: ScreenshotData ->
+        displayIds.forEach { displayId: Int ->
             dispatchToController(
-                screenshotData = screenshotData,
+                rawScreenshotData = ScreenshotData.fromRequest(screenshotRequest, displayId),
                 onSaved =
-                    if (screenshotData.displayId == Display.DEFAULT_DISPLAY) onSaved else { _ -> },
-                callback = resultCallbackWrapper.createCallbackForId(screenshotData.displayId)
+                    if (displayId == Display.DEFAULT_DISPLAY) {
+                        onSaved
+                    } else { _ -> },
+                callback = resultCallbackWrapper.createCallbackForId(displayId)
             )
         }
     }
 
-    /** Creates a [ScreenshotData] for each display. */
-    private suspend fun ScreenshotRequest.oneForEachDisplay(
-        displayIds: List<Int>
-    ): List<ScreenshotData> {
-        return displayIds
-            .map { displayId -> ScreenshotData.fromRequest(this, displayId) }
-            .map { screenshotData: ScreenshotData ->
-                screenshotRequestProcessor.process(screenshotData)
-            }
-    }
-
-    private fun dispatchToController(
-        screenshotData: ScreenshotData,
+    /** All logging should be triggered only by this method. */
+    private suspend fun dispatchToController(
+        rawScreenshotData: ScreenshotData,
         onSaved: (Uri) -> Unit,
         callback: RequestCallback
     ) {
+        // Let's wait before logging "screenshot requested", as we should log the processed
+        // ScreenshotData.
+        val screenshotData =
+            try {
+                screenshotRequestProcessor.process(rawScreenshotData)
+            } catch (e: RequestProcessorException) {
+                Log.e(TAG, "Failed to process screenshot request!", e)
+                logScreenshotRequested(rawScreenshotData)
+                onFailedScreenshotRequest(rawScreenshotData, callback)
+                return
+            }
+
+        logScreenshotRequested(screenshotData)
+        Log.d(TAG, "Screenshot request: $screenshotData")
+        try {
+            getScreenshotController(screenshotData.displayId)
+                .handleScreenshot(screenshotData, onSaved, callback)
+        } catch (e: IllegalStateException) {
+            Log.e(TAG, "Error while ScreenshotController was handling ScreenshotData!", e)
+            onFailedScreenshotRequest(screenshotData, callback)
+            return // After a failure log, nothing else should run.
+        }
+    }
+
+    /**
+     * This should be logged also in case of failed requests, before the [SCREENSHOT_CAPTURE_FAILED]
+     * event.
+     */
+    private fun logScreenshotRequested(screenshotData: ScreenshotData) {
         uiEventLogger.log(
             ScreenshotEvent.getScreenshotSource(screenshotData.source),
             0,
             screenshotData.packageNameString
         )
-        Log.d(TAG, "Screenshot request: $screenshotData")
-        getScreenshotController(screenshotData.displayId)
-            .handleScreenshot(screenshotData, onSaved, callback)
+    }
+
+    private fun onFailedScreenshotRequest(
+        screenshotData: ScreenshotData,
+        callback: RequestCallback
+    ) {
+        uiEventLogger.log(SCREENSHOT_CAPTURE_FAILED, 0, screenshotData.packageNameString)
+        getNotificationController(screenshotData.displayId)
+            .notifyScreenshotError(R.string.screenshot_failed_to_capture_text)
+        callback.reportError()
     }
 
     private fun getDisplaysToScreenshot(requestType: Int): List<Int> {
@@ -135,7 +167,15 @@
     }
 
     private fun getScreenshotController(id: Int): ScreenshotController {
-        return screenshotControllers.computeIfAbsent(id) { screenshotControllerFactory.create(id) }
+        return screenshotControllers.computeIfAbsent(id) {
+            screenshotControllerFactory.create(id, /* showUIOnExternalDisplay= */ false)
+        }
+    }
+
+    private fun getNotificationController(id: Int): ScreenshotNotificationsController {
+        return notificationControllers.computeIfAbsent(id) {
+            screenshotNotificationControllerFactory.create(id)
+        }
     }
 
     /** 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..0991c9a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -113,7 +113,8 @@
     @Inject
     public TakeScreenshotService(ScreenshotController.Factory screenshotControllerFactory,
             UserManager userManager, DevicePolicyManager devicePolicyManager,
-            UiEventLogger uiEventLogger, ScreenshotNotificationsController notificationsController,
+            UiEventLogger uiEventLogger,
+            ScreenshotNotificationsController.Factory notificationsControllerFactory,
             Context context, @Background Executor bgExecutor, FeatureFlags featureFlags,
             RequestProcessor processor, Provider<TakeScreenshotExecutor> takeScreenshotExecutor) {
         if (DEBUG_SERVICE) {
@@ -123,7 +124,7 @@
         mUserManager = userManager;
         mDevicePolicyManager = devicePolicyManager;
         mUiEventLogger = uiEventLogger;
-        mNotificationsController = notificationsController;
+        mNotificationsController = notificationsControllerFactory.create(Display.DEFAULT_DISPLAY);
         mContext = context;
         mBgExecutor = bgExecutor;
         mFeatureFlags = featureFlags;
@@ -132,7 +133,8 @@
         if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
             mScreenshot = null;
         } else {
-            mScreenshot = screenshotControllerFactory.create(Display.DEFAULT_DISPLAY);
+            mScreenshot = screenshotControllerFactory.create(
+                    Display.DEFAULT_DISPLAY, /* showUIOnExternalDisplay= */ false);
         }
     }
 
@@ -245,15 +247,17 @@
         Log.d(TAG, "Processing screenshot data");
 
 
-        ScreenshotData screenshotData = ScreenshotData.fromRequest(
-                request, Display.DEFAULT_DISPLAY);
+        if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
+            mTakeScreenshotExecutor.get().executeScreenshotsAsync(request, onSaved, callback);
+            return;
+        }
+        // TODO(b/295143676): Delete the following after the flag is released.
         try {
-            if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
-                mTakeScreenshotExecutor.get().executeScreenshotsAsync(request, onSaved, callback);
-            } else {
-                mProcessor.processAsync(screenshotData, (data) ->
-                        dispatchToController(data, onSaved, callback));
-            }
+            ScreenshotData screenshotData = ScreenshotData.fromRequest(
+                    request, Display.DEFAULT_DISPLAY);
+            mProcessor.processAsync(screenshotData, (data) ->
+                    dispatchToController(data, onSaved, callback));
+
         } catch (IllegalStateException e) {
             Log.e(TAG, "Failed to process screenshot request!", e);
             logFailedRequest(request);
@@ -263,6 +267,7 @@
         }
     }
 
+    // TODO(b/295143676): Delete this.
     private void dispatchToController(ScreenshotData screenshot,
             Consumer<Uri> uriConsumer, RequestCallback callback) {
         mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0,
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 2ef83dd..8dc97c0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -230,6 +230,8 @@
 import com.android.systemui.util.time.SystemClock;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 import kotlin.Unit;
 
 import java.io.PrintWriter;
@@ -412,9 +414,7 @@
     private int mDisplayLeftInset = 0; // in pixels
 
     @VisibleForTesting
-    KeyguardClockPositionAlgorithm
-            mClockPositionAlgorithm =
-            new KeyguardClockPositionAlgorithm();
+    KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
     private final KeyguardClockPositionAlgorithm.Result
             mClockPositionResult =
             new KeyguardClockPositionAlgorithm.Result();
@@ -777,7 +777,8 @@
             KeyguardViewConfigurator keyguardViewConfigurator,
             KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
             SplitShadeStateController splitShadeStateController,
-            PowerInteractor powerInteractor) {
+            PowerInteractor powerInteractor,
+            KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm) {
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onKeyguardFadingAwayChanged() {
@@ -805,6 +806,7 @@
         mKeyguardInteractor = keyguardInteractor;
         mPowerInteractor = powerInteractor;
         mKeyguardViewConfigurator = keyguardViewConfigurator;
+        mClockPositionAlgorithm = keyguardClockPositionAlgorithm;
         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
@@ -1444,9 +1446,7 @@
                     mBarState);
         }
 
-        if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
-            setKeyguardVisibility(mBarState, false);
-        } else {
+        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
             setKeyguardBottomAreaVisibility(mBarState, false);
         }
 
@@ -2356,14 +2356,6 @@
         }
     }
 
-    private void setKeyguardVisibility(int statusBarState, boolean goingToFullShade) {
-        mKeyguardInteractor.setKeyguardRootVisibility(
-            statusBarState,
-            goingToFullShade,
-            mIsOcclusionTransitionRunning
-        );
-    }
-
     @Deprecated
     private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {
         mKeyguardBottomArea.animate().cancel();
@@ -3379,6 +3371,7 @@
         mBlockingExpansionForCurrentTouch = isTracking();
     }
 
+    @NeverCompile
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println(TAG + ":");
@@ -4440,11 +4433,13 @@
                     && statusBarState == KEYGUARD) {
                 // This means we're doing the screen off animation - position the keyguard status
                 // view where it'll be on AOD, so we can animate it in.
-                mKeyguardStatusViewController.updatePosition(
-                        mClockPositionResult.clockX,
-                        mClockPositionResult.clockYFullyDozing,
-                        mClockPositionResult.clockScale,
-                        false /* animate */);
+                if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                    mKeyguardStatusViewController.updatePosition(
+                            mClockPositionResult.clockX,
+                            mClockPositionResult.clockYFullyDozing,
+                            mClockPositionResult.clockScale,
+                            false /* animate */);
+                }
             }
 
             mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
@@ -4453,9 +4448,7 @@
                     goingToFullShade,
                     mBarState);
 
-            if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
-                setKeyguardVisibility(statusBarState, goingToFullShade);
-            } else {
+            if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
                 setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
             }
 
@@ -4559,7 +4552,12 @@
     public void showAodUi() {
         setDozing(true /* dozing */, false /* animate */);
         mStatusBarStateController.setUpcomingState(KEYGUARD);
-        mStatusBarStateListener.onStateChanged(KEYGUARD);
+
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+            mStatusBarStateController.setState(KEYGUARD);
+        } else {
+            mStatusBarStateListener.onStateChanged(KEYGUARD);
+        }
         mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
         setExpandedFraction(1f);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 5414b3f..d05dfe2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -48,7 +48,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor;
+import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.shared.model.TransitionState;
@@ -106,7 +106,7 @@
     private final NotificationInsetsController mNotificationInsetsController;
     private final boolean mIsTrackpadCommonEnabled;
     private final FeatureFlags mFeatureFlags;
-    private final KeyEventInteractor mKeyEventInteractor;
+    private final SysUIKeyEventHandler mSysUIKeyEventHandler;
     private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
     private GestureDetector mPulsingWakeupGestureHandler;
@@ -185,7 +185,7 @@
             SystemClock clock,
             BouncerMessageInteractor bouncerMessageInteractor,
             BouncerLogger bouncerLogger,
-            KeyEventInteractor keyEventInteractor,
+            SysUIKeyEventHandler sysUIKeyEventHandler,
             PrimaryBouncerInteractor primaryBouncerInteractor,
             AlternateBouncerInteractor alternateBouncerInteractor) {
         mLockscreenShadeTransitionController = transitionController;
@@ -214,7 +214,7 @@
         mNotificationInsetsController = notificationInsetsController;
         mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON);
         mFeatureFlags = featureFlags;
-        mKeyEventInteractor = keyEventInteractor;
+        mSysUIKeyEventHandler = sysUIKeyEventHandler;
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
         mAlternateBouncerInteractor = alternateBouncerInteractor;
 
@@ -529,17 +529,17 @@
 
             @Override
             public boolean interceptMediaKey(KeyEvent event) {
-                return mKeyEventInteractor.interceptMediaKey(event);
+                return mSysUIKeyEventHandler.interceptMediaKey(event);
             }
 
             @Override
             public boolean dispatchKeyEventPreIme(KeyEvent event) {
-                return mKeyEventInteractor.dispatchKeyEventPreIme(event);
+                return mSysUIKeyEventHandler.dispatchKeyEventPreIme(event);
             }
 
             @Override
             public boolean dispatchKeyEvent(KeyEvent event) {
-                return mKeyEventInteractor.dispatchKeyEvent(event);
+                return mSysUIKeyEventHandler.dispatchKeyEvent(event);
             }
         });
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 9b74ac4..3bbb2cf 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -105,6 +105,8 @@
 import com.android.systemui.util.LargeScreenUtils;
 import com.android.systemui.util.kotlin.JavaAdapter;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 import dagger.Lazy;
 
 import java.io.PrintWriter;
@@ -2015,6 +2017,7 @@
                 (int) ((y - getInitialTouchY()) / displayDensity), (int) (vel / displayDensity));
     }
 
+    @NeverCompile
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println(TAG + ":");
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/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 6117f9f..e487a6f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -261,7 +261,7 @@
                 when (state) {
                     is ObservableTransitionState.Idle -> false
                     is ObservableTransitionState.Transition ->
-                        state.isInitiatedByUserInput &&
+                        state.isUserInputDriven &&
                             (state.toScene == sceneKey || state.fromScene == sceneKey)
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index d24f9d8..77b0958 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -18,6 +18,8 @@
 
 import android.view.View;
 
+import androidx.annotation.Nullable;
+
 import com.android.app.animation.Interpolators;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -113,7 +115,16 @@
         fadeIn(view, ANIMATION_DURATION_LENGTH, 0);
     }
 
+    public static void fadeIn(final View view, Runnable endRunnable) {
+        fadeIn(view, ANIMATION_DURATION_LENGTH, /* delay= */ 0, endRunnable);
+    }
+
     public static void fadeIn(final View view, long duration, int delay) {
+        fadeIn(view, duration, delay, /* endRunnable= */ null);
+    }
+
+    public static void fadeIn(final View view, long duration, int delay,
+            @Nullable Runnable endRunnable) {
         view.animate().cancel();
         if (view.getVisibility() == View.INVISIBLE) {
             view.setAlpha(0.0f);
@@ -124,7 +135,7 @@
                 .setDuration(duration)
                 .setStartDelay(delay)
                 .setInterpolator(Interpolators.ALPHA_IN)
-                .withEndAction(null);
+                .withEndAction(endRunnable);
         if (view.hasOverlappingRendering() && view.getLayerType() != View.LAYER_TYPE_HARDWARE) {
             view.animate().withLayer();
         }
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/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index d4b6dfb..61ebcc0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.statusbar;
 
+
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.KeyguardManager;
@@ -48,10 +49,10 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
@@ -535,7 +536,8 @@
     public void cleanUpRemoteInputForUserRemoval(NotificationEntry entry) {
         if (isRemoteInputActive(entry)) {
             entry.mRemoteEditImeVisible = false;
-            mRemoteInputController.removeRemoteInput(entry, null);
+            mRemoteInputController.removeRemoteInput(entry, null,
+                    /* reason= */"RemoteInputManager#cleanUpRemoteInputForUserRemoval");
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 09ad55e..23b697e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -37,11 +37,11 @@
 import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.SystemBarUtils;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.ViewRefactorFlag;
+import com.android.systemui.flags.RefactorFlag;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.res.R;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.SourceType;
@@ -96,10 +96,10 @@
     private float mCornerAnimationDistance;
     private NotificationShelfController mController;
     private float mActualWidth = -1;
-    private final ViewRefactorFlag mSensitiveRevealAnim =
-            new ViewRefactorFlag(Flags.SENSITIVE_REVEAL_ANIM);
-    private final ViewRefactorFlag mShelfRefactor =
-            new ViewRefactorFlag(Flags.NOTIFICATION_SHELF_REFACTOR);
+    private final RefactorFlag mSensitiveRevealAnim =
+            RefactorFlag.forView(Flags.SENSITIVE_REVEAL_ANIM);
+    private final RefactorFlag mShelfRefactor =
+            RefactorFlag.forView(Flags.NOTIFICATION_SHELF_REFACTOR);
     private boolean mCanModifyColorOfNotifications;
     private boolean mCanInteract;
     private NotificationStackScrollLayout mHostLayout;
@@ -133,7 +133,7 @@
 
     public void bind(AmbientState ambientState,
                      NotificationStackScrollLayoutController hostLayoutController) {
-        mShelfRefactor.assertDisabled();
+        mShelfRefactor.assertInLegacyMode();
         mAmbientState = ambientState;
         mHostLayoutController = hostLayoutController;
         hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> {
@@ -143,7 +143,7 @@
 
     public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout,
             NotificationRoundnessManager roundnessManager) {
-        if (!mShelfRefactor.expectEnabled()) return;
+        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
         mAmbientState = ambientState;
         mHostLayout = hostLayout;
         mRoundnessManager = roundnessManager;
@@ -964,7 +964,7 @@
 
     @Override
     public void onStateChanged(int newState) {
-        mShelfRefactor.assertDisabled();
+        mShelfRefactor.assertInLegacyMode();
         mStatusBarState = newState;
         updateInteractiveness();
     }
@@ -1022,17 +1022,17 @@
     }
 
     public void setController(NotificationShelfController notificationShelfController) {
-        mShelfRefactor.assertDisabled();
+        mShelfRefactor.assertInLegacyMode();
         mController = notificationShelfController;
     }
 
     public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) {
-        if (!mShelfRefactor.expectEnabled()) return;
+        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
         mCanModifyColorOfNotifications = canModifyColorOfNotifications;
     }
 
     public void setCanInteract(boolean canInteract) {
-        if (!mShelfRefactor.expectEnabled()) return;
+        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
         mCanInteract = canInteract;
         updateInteractiveness();
     }
@@ -1050,7 +1050,7 @@
     }
 
     public void requestRoundnessResetFor(ExpandableView child) {
-        if (!mShelfRefactor.expectEnabled()) return;
+        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
         child.requestRoundnessReset(SHELF_SCROLL);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index d5e4902..63282d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -31,6 +31,8 @@
 import com.android.systemui.statusbar.policy.RemoteInputView;
 import com.android.systemui.util.DumpUtilsKt;
 
+import com.google.errorprone.annotations.CompileTimeConstant;
+
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Objects;
@@ -96,7 +98,8 @@
      *              the entry is only removed if the token matches the last added token for this
      *              entry. If null, the entry is removed regardless.
      */
-    public void removeRemoteInput(NotificationEntry entry, Object token) {
+    public void removeRemoteInput(NotificationEntry entry, Object token,
+            @CompileTimeConstant String reason) {
         Objects.requireNonNull(entry);
         if (entry.mRemoteEditImeVisible && entry.mRemoteEditImeAnimatingAway) {
             mLogger.logRemoveRemoteInput(
@@ -104,9 +107,12 @@
                     true /* remoteEditImeVisible */,
                     true /* remoteEditImeAnimatingAway */,
                     isRemoteInputActive(entry) /* isRemoteInputActiveForEntry */,
-                    isRemoteInputActive() /* isRemoteInputActive */);
+                    isRemoteInputActive() /* isRemoteInputActive */,
+                    reason /* reason */,
+                    entry.getNotificationStyle()/* notificationStyle */);
             return;
         }
+
         // If the view is being removed, this may be called even though we're not active
         boolean remoteInputActiveForEntry = isRemoteInputActive(entry);
         mLogger.logRemoveRemoteInput(
@@ -114,7 +120,9 @@
                 entry.mRemoteEditImeVisible /* remoteEditImeVisible */,
                 entry.mRemoteEditImeAnimatingAway /* remoteEditImeAnimatingAway */,
                 remoteInputActiveForEntry /* isRemoteInputActiveForEntry */,
-                isRemoteInputActive()/* isRemoteInputActive */);
+                isRemoteInputActive()/* isRemoteInputActive */,
+                reason/* reason */,
+                entry.getNotificationStyle()/* notificationStyle */);
 
         if (!remoteInputActiveForEntry) return;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index f616b91..3a4ad0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -51,6 +51,7 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.Interpolator;
 
+import androidx.annotation.Nullable;
 import androidx.core.graphics.ColorUtils;
 
 import com.android.app.animation.Interpolators;
@@ -959,12 +960,17 @@
     }
 
     public void setDozing(boolean dozing, boolean fade, long delay) {
+        setDozing(dozing, fade, delay, /* onChildCompleted= */ null);
+    }
+
+    public void setDozing(boolean dozing, boolean fade, long delay,
+            @Nullable Runnable endRunnable) {
         mDozer.setDozing(f -> {
             mDozeAmount = f;
             updateDecorColor();
             updateIconColor();
             updateAllowAnimation();
-        }, dozing, fade, delay, this);
+        }, dozing, fade, delay, this, endRunnable);
     }
 
     private void updateAllowAnimation() {
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/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 9db61c6..fc84973 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -83,6 +83,8 @@
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.CarrierConfigTracker;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -1154,6 +1156,7 @@
     }
 
     /** */
+    @NeverCompile
     public void dump(PrintWriter pw, String[] args) {
         pw.println("NetworkController state:");
         pw.println("  mUserSetup=" + mUserSetup);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
index 167efc7..dc0eb7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
@@ -24,6 +24,8 @@
 import android.view.View;
 import android.widget.ImageView;
 
+import androidx.annotation.Nullable;
+
 import com.android.app.animation.Interpolators;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -81,6 +83,11 @@
 
     public void setDozing(Consumer<Float> listener, boolean dozing,
             boolean animate, long delay, View view) {
+        setDozing(listener, dozing, animate, delay, view, /* endRunnable= */ null);
+    }
+
+    public void setDozing(Consumer<Float> listener, boolean dozing,
+            boolean animate, long delay, View view, @Nullable Runnable endRunnable) {
         if (animate) {
             startIntensityAnimation(a -> listener.accept((Float) a.getAnimatedValue()), dozing,
                     delay,
@@ -89,6 +96,9 @@
                         @Override
                         public void onAnimationEnd(Animator animation) {
                             view.setTag(DOZE_ANIMATOR_TAG, null);
+                            if (endRunnable != null) {
+                                endRunnable.run();
+                            }
                         }
 
                         @Override
@@ -102,6 +112,9 @@
                 animator.cancel();
             }
             listener.accept(dozing ? 1f : 0f);
+            if (endRunnable != null) {
+                endRunnable.run();
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
index 39b999c..9777031 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
@@ -52,20 +52,25 @@
         remoteEditImeVisible: Boolean,
         remoteEditImeAnimatingAway: Boolean,
         isRemoteInputActiveForEntry: Boolean,
-        isRemoteInputActive: Boolean
+        isRemoteInputActive: Boolean,
+        reason: String,
+        notificationStyle: String
     ) =
         logBuffer.log(
             TAG,
             DEBUG,
             {
                 str1 = entryKey
+                str2 = reason
+                str3 = notificationStyle
                 bool1 = remoteEditImeVisible
                 bool2 = remoteEditImeAnimatingAway
                 bool3 = isRemoteInputActiveForEntry
                 bool4 = isRemoteInputActive
             },
             {
-                "removeRemoteInput entry: $str1, remoteEditImeVisible: $bool1" +
+                "removeRemoteInput reason: $str2 entry: $str1" +
+                    ", style: $str3, remoteEditImeVisible: $bool1" +
                     ", remoteEditImeAnimatingAway: $bool2, isRemoteInputActiveForEntry: $bool3" +
                     ", isRemoteInputActive: $bool4"
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index 328a741..3e9c6fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -3,10 +3,10 @@
 import android.util.FloatProperty
 import android.view.View
 import androidx.annotation.FloatRange
-import com.android.systemui.res.R
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.flags.ViewRefactorFlag
+import com.android.systemui.flags.RefactorFlag
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.notification.stack.AnimationProperties
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import kotlin.math.abs
@@ -323,7 +323,7 @@
     internal var maxRadius = maxRadius
         private set
 
-    internal val newHeadsUpAnim = ViewRefactorFlag(featureFlags, Flags.IMPROVED_HUN_ANIMATIONS)
+    internal val newHeadsUpAnim = RefactorFlag.forView(Flags.IMPROVED_HUN_ANIMATIONS, featureFlags)
 
     /** Animatable for top roundness */
     private val topAnimatable = topAnimatable(roundable)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index affd2d1..4573d59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -979,6 +979,19 @@
         return mExpandAnimationRunning;
     }
 
+    /**
+     * @return NotificationStyle
+     */
+    public String getNotificationStyle() {
+        if (isSummaryWithChildren()) {
+            return "summary";
+        }
+
+        final Class<? extends Notification.Style> style =
+                getSbn().getNotification().getNotificationStyle();
+        return style == null ? "nostyle" : style.getSimpleName();
+    }
+
     /** Information about a suggestion that is being edited. */
     public static class EditedSuggestionInfo {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
index 9d56a8e..362786e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
@@ -115,6 +115,7 @@
      *
      * @deprecated Use {@link #onRankingApplied()} instead.
      */
+    @Deprecated
     default void onRankingUpdate(NotificationListenerService.RankingMap rankingMap) {
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 733d774..8561869 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -53,6 +53,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.data.NotificationDataLayerModule;
 import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
 import com.android.systemui.statusbar.notification.icon.ConversationIconManager;
 import com.android.systemui.statusbar.notification.icon.IconManager;
@@ -95,6 +96,7 @@
         CoordinatorsModule.class,
         KeyguardNotificationVisibilityProviderModule.class,
         ShadeEventsModule.class,
+        NotificationDataLayerModule.class,
         NotifPipelineChoreographerModule.class,
         NotificationSectionHeadersModule.class,
         NotificationListViewModelModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
new file mode 100644
index 0000000..5435fb5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.data
+
+import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardStateRepositoryModule
+import dagger.Module
+
+@Module(includes = [NotificationsKeyguardStateRepositoryModule::class])
+interface NotificationDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
new file mode 100644
index 0000000..cf03d1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.data.repository
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** View-states pertaining to notifications on the keyguard. */
+interface NotificationsKeyguardViewStateRepository {
+    /** Are notifications fully hidden from view? */
+    val areNotificationsFullyHidden: Flow<Boolean>
+
+    /** Is a pulse expansion occurring? */
+    val isPulseExpanding: Flow<Boolean>
+}
+
+@Module
+interface NotificationsKeyguardStateRepositoryModule {
+    @Binds
+    fun bindImpl(
+        impl: NotificationsKeyguardViewStateRepositoryImpl
+    ): NotificationsKeyguardViewStateRepository
+}
+
+@SysUISingleton
+class NotificationsKeyguardViewStateRepositoryImpl
+@Inject
+constructor(
+    wakeUpCoordinator: NotificationWakeUpCoordinator,
+) : NotificationsKeyguardViewStateRepository {
+    override val areNotificationsFullyHidden: Flow<Boolean> = conflatedCallbackFlow {
+        val listener =
+            object : NotificationWakeUpCoordinator.WakeUpListener {
+                override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
+                    trySend(isFullyHidden)
+                }
+            }
+        trySend(wakeUpCoordinator.notificationsFullyHidden)
+        wakeUpCoordinator.addListener(listener)
+        awaitClose { wakeUpCoordinator.removeListener(listener) }
+    }
+
+    override val isPulseExpanding: Flow<Boolean> = conflatedCallbackFlow {
+        val listener =
+            object : NotificationWakeUpCoordinator.WakeUpListener {
+                override fun onPulseExpansionChanged(expandingChanged: Boolean) {
+                    trySend(expandingChanged)
+                }
+            }
+        trySend(wakeUpCoordinator.isPulseExpanding())
+        wakeUpCoordinator.addListener(listener)
+        awaitClose { wakeUpCoordinator.removeListener(listener) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt
new file mode 100644
index 0000000..87b8e55
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Domain logic pertaining to notifications on the keyguard. */
+class NotificationsKeyguardInteractor
+@Inject
+constructor(
+    repository: NotificationsKeyguardViewStateRepository,
+) {
+    /** Is a pulse expansion occurring? */
+    val isPulseExpanding: Flow<Boolean> = repository.isPulseExpanding
+
+    /** Are notifications fully hidden from view? */
+    val areNotificationsFullyHidden: Flow<Boolean> = repository.areNotificationsFullyHidden
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 26db5f2..e74b3fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -11,10 +11,10 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.row;
+package com.android.systemui.statusbar.notification.footer.ui.view;
 
 import static android.graphics.PorterDuff.Mode.SRC_ATOP;
 
@@ -35,6 +35,8 @@
 
 import com.android.settingslib.Utils;
 import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.row.FooterViewButton;
+import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.ViewState;
 import com.android.systemui.util.DumpUtilsKt;
@@ -93,6 +95,7 @@
         updateColors();
     }
 
+    /** Show a message instead of the footer buttons. */
     public void setFooterLabelVisible(boolean isVisible) {
         if (isVisible) {
             mManageButton.setVisibility(View.GONE);
@@ -105,14 +108,22 @@
         }
     }
 
+    /** Set onClickListener for the manage/history button. */
     public void setManageButtonClickListener(OnClickListener listener) {
         mManageButton.setOnClickListener(listener);
     }
 
+    /** Set onClickListener for the clear all (end) button. */
     public void setClearAllButtonClickListener(OnClickListener listener) {
         mClearAllButton.setOnClickListener(listener);
     }
 
+    /**
+     * Whether the touch is outside the Clear all button.
+     *
+     * TODO(b/293167744): This is an artifact from the time when we could press underneath the
+     * shade to dismiss it. Check if it's safe to remove.
+     */
     public boolean isOnEmptySpace(float touchX, float touchY) {
         return touchX < mContent.getX()
                 || touchX > mContent.getX() + mContent.getWidth()
@@ -120,6 +131,7 @@
                 || touchY > mContent.getY() + mContent.getHeight();
     }
 
+    /** Show "History" instead of "Manage" on the start button. */
     public void showHistory(boolean showHistory) {
         if (mShowHistory == showHistory) {
             return;
@@ -141,6 +153,7 @@
                 .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null);
     }
 
+    /** Whether the start button shows "History" (true) or "Manage" (false). */
     public boolean isHistoryShown() {
         return mShowHistory;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
index a77e67b..eb5c1fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
@@ -26,26 +26,21 @@
 import androidx.annotation.ColorInt
 import androidx.annotation.VisibleForTesting
 import androidx.collection.ArrayMap
-import com.android.app.animation.Interpolators
 import com.android.internal.statusbar.StatusBarIcon
 import com.android.internal.util.ContrastColorUtil
 import com.android.settingslib.Utils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.demomode.DemoMode
 import com.android.systemui.demomode.DemoModeController
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.flags.Flags.NEW_AOD_TRANSITION
-import com.android.systemui.flags.ViewRefactorFlag
+import com.android.systemui.flags.RefactorFlag
 import com.android.systemui.plugins.DarkIconDispatcher
-import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.CrossFadeHelper
 import com.android.systemui.statusbar.NotificationListener
 import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.NotificationShelfController
 import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.NotificationUtils
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
 import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -60,6 +55,7 @@
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.statusbar.phone.NotificationIconContainer
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
@@ -79,9 +75,9 @@
 @Inject
 constructor(
     private val context: Context,
-    private val statusBarStateController: StatusBarStateController,
     private val wakeUpCoordinator: NotificationWakeUpCoordinator,
     private val bypassController: KeyguardBypassController,
+    private val configurationController: ConfigurationController,
     private val mediaManager: NotificationMediaManager,
     notificationListener: NotificationListener,
     private val dozeParameters: DozeParameters,
@@ -89,7 +85,7 @@
     private val bubblesOptional: Optional<Bubbles>,
     demoModeController: DemoModeController,
     darkIconDispatcher: DarkIconDispatcher,
-    private val featureFlags: FeatureFlags,
+    private val featureFlags: FeatureFlagsClassic,
     private val statusBarWindowController: StatusBarWindowController,
     private val screenOffAnimationController: ScreenOffAnimationController,
     private val shelfIconsViewModel: NotificationIconContainerShelfViewModel,
@@ -98,14 +94,12 @@
 ) :
     NotificationIconAreaController,
     DarkIconDispatcher.DarkReceiver,
-    StatusBarStateController.StateListener,
     NotificationWakeUpCoordinator.WakeUpListener,
     DemoMode {
 
     private val contrastColorUtil: ContrastColorUtil = ContrastColorUtil.getInstance(context)
     private val updateStatusBarIcons = Runnable { updateStatusBarIcons() }
-    private val shelfRefactor = ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR)
-    private val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)
+    private val shelfRefactor = RefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR)
     private val tintAreas = ArrayList<Rect>()
 
     private var iconSize = 0
@@ -119,7 +113,6 @@
     private var aodBindJob: DisposableHandle? = null
     private var aodIconAppearTranslation = 0
     private var aodIconTint = 0
-    private var aodIconsVisible = false
     private var showLowPriority = true
 
     @VisibleForTesting
@@ -132,7 +125,6 @@
         }
 
     init {
-        statusBarStateController.addCallback(this)
         wakeUpCoordinator.addListener(this)
         demoModeController.addCallback(this)
         notificationListener.addNotificationSettingsListener(settingsListener)
@@ -160,8 +152,11 @@
             NotificationIconContainerViewBinder.bind(
                 aodIcons,
                 aodIconsViewModel,
+                configurationController,
+                dozeParameters,
+                featureFlags,
+                screenOffAnimationController,
             )
-        updateAodIconsVisibility(animate = false, forceUpdate = changed)
         if (changed) {
             updateAodNotificationIcons()
         }
@@ -172,13 +167,16 @@
         NotificationShelfViewBinderWrapperControllerImpl.unsupported
 
     override fun setShelfIcons(icons: NotificationIconContainer) {
-        if (shelfRefactor.expectEnabled()) {
-            NotificationIconContainerViewBinder.bind(
-                icons,
-                shelfIconsViewModel,
-            )
-            shelfIcons = icons
-        }
+        if (shelfRefactor.isUnexpectedlyInLegacyMode()) return
+        NotificationIconContainerViewBinder.bind(
+            icons,
+            shelfIconsViewModel,
+            configurationController,
+            dozeParameters,
+            featureFlags,
+            screenOffAnimationController,
+        )
+        shelfIcons = icons
     }
 
     override fun onDensityOrFontScaleChanged(context: Context) {
@@ -249,20 +247,8 @@
         notificationIcons!!.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate)
     }
 
-    override fun onDozingChanged(isDozing: Boolean) {
-        if (aodIcons == null) {
-            return
-        }
-        val animate = (dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking)
-        aodIcons!!.setDozing(isDozing, animate, 0)
-    }
-
     override fun setAnimationsEnabled(enabled: Boolean) = unsupported
 
-    override fun onStateChanged(newState: Int) {
-        updateAodIconsVisibility(animate = false, forceUpdate = false)
-    }
-
     override fun onThemeChanged() {
         reloadAodColor()
         updateAodIconColors()
@@ -272,53 +258,11 @@
         return if (aodIcons == null) 0 else aodIcons!!.height
     }
 
-    @VisibleForTesting
-    fun appearAodIcons() {
-        if (aodIcons == null) {
-            return
-        }
-        if (screenOffAnimationController.shouldAnimateAodIcons()) {
-            if (!statusViewMigrated) {
-                aodIcons!!.translationY = -aodIconAppearTranslation.toFloat()
-            }
-            aodIcons!!.alpha = 0f
-            animateInAodIconTranslation()
-            aodIcons!!
-                .animate()
-                .alpha(1f)
-                .setInterpolator(Interpolators.LINEAR)
-                .setDuration(AOD_ICONS_APPEAR_DURATION)
-                .start()
-        } else {
-            aodIcons!!.alpha = 1.0f
-            if (!statusViewMigrated) {
-                aodIcons!!.translationY = 0f
-            }
-        }
-    }
-
     override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
-        var animate = true
-        if (!bypassController.bypassEnabled) {
-            animate = dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking
-            if (!featureFlags.isEnabled(NEW_AOD_TRANSITION)) {
-                // We only want the appear animations to happen when the notifications get fully
-                // hidden,
-                // since otherwise the unhide animation overlaps
-                animate = animate and isFullyHidden
-            }
-        }
-        updateAodIconsVisibility(animate, false /* force */)
         updateAodNotificationIcons()
         updateAodIconColors()
     }
 
-    override fun onPulseExpansionChanged(expandingChanged: Boolean) {
-        if (expandingChanged) {
-            updateAodIconsVisibility(animate = true, forceUpdate = false)
-        }
-    }
-
     override fun demoCommands(): List<String> {
         val commands = ArrayList<String>()
         commands.add(DemoMode.COMMAND_NOTIFICATIONS)
@@ -352,6 +296,10 @@
         NotificationIconContainerViewBinder.bind(
             notificationIcons!!,
             statusBarIconsViewModel,
+            configurationController,
+            dozeParameters,
+            featureFlags,
+            screenOffAnimationController,
         )
     }
 
@@ -602,17 +550,6 @@
         v.setDecorColor(tint)
     }
 
-    private fun animateInAodIconTranslation() {
-        if (!statusViewMigrated) {
-            aodIcons!!
-                .animate()
-                .setInterpolator(Interpolators.DECELERATE_QUINT)
-                .translationY(0f)
-                .setDuration(AOD_ICONS_APPEAR_DURATION)
-                .start()
-        }
-    }
-
     private fun reloadAodColor() {
         aodIconTint =
             Utils.getColorAttrDefaultColor(
@@ -635,69 +572,7 @@
         }
     }
 
-    private fun updateAodIconsVisibility(animate: Boolean, forceUpdate: Boolean) {
-        if (aodIcons == null) {
-            return
-        }
-        var visible = (bypassController.bypassEnabled || wakeUpCoordinator.notificationsFullyHidden)
-
-        // Hide the AOD icons if we're not in the KEYGUARD state unless the screen off animation is
-        // playing, in which case we want them to be visible since we're animating in the AOD UI and
-        // will be switching to KEYGUARD shortly.
-        if (
-            statusBarStateController.state != StatusBarState.KEYGUARD &&
-                !screenOffAnimationController.shouldShowAodIconsWhenShade()
-        ) {
-            visible = false
-        }
-        if (visible && wakeUpCoordinator.isPulseExpanding() && !bypassController.bypassEnabled) {
-            visible = false
-        }
-        if (aodIconsVisible != visible || forceUpdate) {
-            aodIconsVisible = visible
-            aodIcons!!.animate().cancel()
-            if (animate) {
-                if (featureFlags.isEnabled(NEW_AOD_TRANSITION)) {
-                    // Let's make sure the icon are translated to 0, since we cancelled it above
-                    animateInAodIconTranslation()
-                    if (aodIconsVisible) {
-                        CrossFadeHelper.fadeIn(aodIcons)
-                    } else {
-                        CrossFadeHelper.fadeOut(aodIcons)
-                    }
-                } else {
-                    val wasFullyInvisible = aodIcons!!.visibility != View.VISIBLE
-                    if (aodIconsVisible) {
-                        if (wasFullyInvisible) {
-                            // No fading here, let's just appear the icons instead!
-                            aodIcons!!.visibility = View.VISIBLE
-                            aodIcons!!.alpha = 1.0f
-                            appearAodIcons()
-                        } else {
-                            // Let's make sure the icon are translated to 0, since we cancelled it
-                            // above
-                            animateInAodIconTranslation()
-                            // We were fading out, let's fade in instead
-                            CrossFadeHelper.fadeIn(aodIcons)
-                        }
-                    } else {
-                        // Let's make sure the icon are translated to 0, since we cancelled it above
-                        animateInAodIconTranslation()
-                        CrossFadeHelper.fadeOut(aodIcons)
-                    }
-                }
-            } else {
-                aodIcons!!.alpha = 1.0f
-                if (!statusViewMigrated) {
-                    aodIcons!!.translationY = 0f
-                }
-                aodIcons!!.visibility = if (visible) View.VISIBLE else View.INVISIBLE
-            }
-        }
-    }
-
     companion object {
-        private const val AOD_ICONS_APPEAR_DURATION: Long = 200
         @ColorInt private val DEFAULT_AOD_ICON_COLOR = -0x1
 
         val unsupported: Nothing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index f8ff3e3..0d2f00a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -15,12 +15,27 @@
  */
 package com.android.systemui.statusbar.notification.icon.ui.viewbinder
 
+import android.content.res.Resources
+import android.view.View
+import androidx.annotation.DimenRes
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.CrossFadeHelper
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel
+import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged
+import com.android.systemui.util.kotlin.stateFlow
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
 
 /** Binds a [NotificationIconContainer] to its [view model][NotificationIconContainerViewModel]. */
@@ -28,11 +43,144 @@
     fun bind(
         view: NotificationIconContainer,
         viewModel: NotificationIconContainerViewModel,
+        configurationController: ConfigurationController,
+        dozeParameters: DozeParameters,
+        featureFlags: FeatureFlagsClassic,
+        screenOffAnimationController: ScreenOffAnimationController,
     ): DisposableHandle {
         return view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.CREATED) {
                 launch { viewModel.animationsEnabled.collect(view::setAnimationsEnabled) }
+                launch {
+                    viewModel.isDozing.collect { (isDozing, animate) ->
+                        val animateIfNotBlanking = animate && !dozeParameters.displayNeedsBlanking
+                        view.setDozing(isDozing, animateIfNotBlanking, /* delay= */ 0) {
+                            viewModel.completeDozeAnimation()
+                        }
+                    }
+                }
+                // TODO(278765923): this should live where AOD is bound, not inside of the NIC
+                //  view-binder
+                launch {
+                    val iconAppearTranslation =
+                        view.resources.getConfigAwareDimensionPixelSize(
+                            this,
+                            configurationController,
+                            R.dimen.shelf_appear_translation,
+                        )
+                    bindVisibility(
+                        viewModel,
+                        view,
+                        featureFlags,
+                        screenOffAnimationController,
+                        iconAppearTranslation,
+                    ) {
+                        viewModel.completeVisibilityAnimation()
+                    }
+                }
             }
         }
     }
+    private suspend fun bindVisibility(
+        viewModel: NotificationIconContainerViewModel,
+        view: NotificationIconContainer,
+        featureFlags: FeatureFlagsClassic,
+        screenOffAnimationController: ScreenOffAnimationController,
+        iconAppearTranslation: StateFlow<Int>,
+        onAnimationEnd: () -> Unit,
+    ) {
+        val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)
+        viewModel.isVisible.collect { (isVisible, animate) ->
+            view.animate().cancel()
+            when {
+                !animate -> {
+                    view.alpha = 1f
+                    if (!statusViewMigrated) {
+                        view.translationY = 0f
+                    }
+                    view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
+                }
+                featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> {
+                    animateInIconTranslation(view, statusViewMigrated)
+                    if (isVisible) {
+                        CrossFadeHelper.fadeIn(view, onAnimationEnd)
+                    } else {
+                        CrossFadeHelper.fadeOut(view, onAnimationEnd)
+                    }
+                }
+                !isVisible -> {
+                    // Let's make sure the icon are translated to 0, since we cancelled it above
+                    animateInIconTranslation(view, statusViewMigrated)
+                    CrossFadeHelper.fadeOut(view, onAnimationEnd)
+                }
+                view.visibility != View.VISIBLE -> {
+                    // No fading here, let's just appear the icons instead!
+                    view.visibility = View.VISIBLE
+                    view.alpha = 1f
+                    appearIcons(
+                        view,
+                        animate = screenOffAnimationController.shouldAnimateAodIcons(),
+                        iconAppearTranslation.value,
+                        statusViewMigrated,
+                    )
+                    onAnimationEnd()
+                }
+                else -> {
+                    // Let's make sure the icons are translated to 0, since we cancelled it above
+                    animateInIconTranslation(view, statusViewMigrated)
+                    // We were fading out, let's fade in instead
+                    CrossFadeHelper.fadeIn(view, onAnimationEnd)
+                }
+            }
+        }
+    }
+
+    private fun appearIcons(
+        view: View,
+        animate: Boolean,
+        iconAppearTranslation: Int,
+        statusViewMigrated: Boolean,
+    ) {
+        if (animate) {
+            if (!statusViewMigrated) {
+                view.translationY = -iconAppearTranslation.toFloat()
+            }
+            view.alpha = 0f
+            animateInIconTranslation(view, statusViewMigrated)
+            view
+                .animate()
+                .alpha(1f)
+                .setInterpolator(Interpolators.LINEAR)
+                .setDuration(AOD_ICONS_APPEAR_DURATION)
+                .start()
+        } else {
+            view.alpha = 1.0f
+            if (!statusViewMigrated) {
+                view.translationY = 0f
+            }
+        }
+    }
+
+    private fun animateInIconTranslation(view: View, statusViewMigrated: Boolean) {
+        if (!statusViewMigrated) {
+            view
+                .animate()
+                .setInterpolator(Interpolators.DECELERATE_QUINT)
+                .translationY(0f)
+                .setDuration(AOD_ICONS_APPEAR_DURATION)
+                .start()
+        }
+    }
+
+    private const val AOD_ICONS_APPEAR_DURATION: Long = 200
 }
+
+fun Resources.getConfigAwareDimensionPixelSize(
+    scope: CoroutineScope,
+    configurationController: ConfigurationController,
+    @DimenRes id: Int,
+): StateFlow<Int> =
+    scope.stateFlow(
+        changedSignals = configurationController.onDensityOrFontScaleChanged,
+        getValue = { getDimensionPixelSize(id) }
+    )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
index 90507fc..3289a3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
@@ -15,19 +15,48 @@
  */
 package com.android.systemui.statusbar.notification.icon.ui.viewmodel
 
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
+import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.ui.AnimatableEvent
+import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.util.ui.toAnimatedValueFlow
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
 
 /** View-model for the row of notification icons displayed on the always-on display. */
+@SysUISingleton
 class NotificationIconContainerAlwaysOnDisplayViewModel
 @Inject
 constructor(
+    private val deviceEntryInteractor: DeviceEntryInteractor,
+    private val dozeParameters: DozeParameters,
+    private val featureFlags: FeatureFlagsClassic,
     keyguardInteractor: KeyguardInteractor,
+    keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
+    screenOffAnimationController: ScreenOffAnimationController,
     shadeInteractor: ShadeInteractor,
 ) : NotificationIconContainerViewModel {
+
+    private val onDozeAnimationComplete = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+    private val onVisAnimationComplete = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+
     override val animationsEnabled: Flow<Boolean> =
         combine(
             shadeInteractor.isShadeTouchable,
@@ -35,4 +64,97 @@
         ) { panelTouchesEnabled, isKeyguardVisible ->
             panelTouchesEnabled && isKeyguardVisible
         }
+
+    override val isDozing: Flow<AnimatedValue<Boolean>> =
+        keyguardTransitionInteractor.startedKeyguardTransitionStep
+            // Determine if we're dozing based on the most recent transition
+            .map { step: TransitionStep ->
+                val isDozing = step.to == KeyguardState.AOD || step.to == KeyguardState.DOZING
+                isDozing to step
+            }
+            // Only emit changes based on whether we've started or stopped dozing
+            .distinctUntilChanged { (wasDozing, _), (isDozing, _) -> wasDozing != isDozing }
+            // Determine whether we need to animate
+            .map { (isDozing, step) ->
+                val animate = step.to == KeyguardState.AOD || step.from == KeyguardState.AOD
+                AnimatableEvent(isDozing, animate)
+            }
+            .distinctUntilChanged()
+            .toAnimatedValueFlow(completionEvents = onDozeAnimationComplete)
+
+    override val isVisible: Flow<AnimatedValue<Boolean>> =
+        combine(
+                keyguardTransitionInteractor.finishedKeyguardState.map { it != KeyguardState.GONE },
+                deviceEntryInteractor.isBypassEnabled,
+                areNotifsFullyHiddenAnimated(),
+                isPulseExpandingAnimated(),
+            ) {
+                onKeyguard: Boolean,
+                bypassEnabled: Boolean,
+                (notifsFullyHidden: Boolean, isAnimatingHide: Boolean),
+                (pulseExpanding: Boolean, isAnimatingPulse: Boolean),
+                ->
+                val isAnimating = isAnimatingHide || isAnimatingPulse
+                when {
+                    // Hide the AOD icons if we're not in the KEYGUARD state unless the screen off
+                    // animation is playing, in which case we want them to be visible if we're
+                    // animating in the AOD UI and will be switching to KEYGUARD shortly.
+                    !onKeyguard && !screenOffAnimationController.shouldShowAodIconsWhenShade() ->
+                        AnimatedValue(false, isAnimating = false)
+                    // If we're bypassing, then we're visible
+                    bypassEnabled -> AnimatedValue(true, isAnimating)
+                    // If we are pulsing (and not bypassing), then we are hidden
+                    pulseExpanding -> AnimatedValue(false, isAnimating)
+                    // If notifs are fully gone, then we're visible
+                    notifsFullyHidden -> AnimatedValue(true, isAnimating)
+                    // Otherwise, we're hidden
+                    else -> AnimatedValue(false, isAnimating)
+                }
+            }
+            .distinctUntilChanged()
+
+    override fun completeDozeAnimation() {
+        onDozeAnimationComplete.tryEmit(Unit)
+    }
+
+    override fun completeVisibilityAnimation() {
+        onVisAnimationComplete.tryEmit(Unit)
+    }
+
+    /** Is there an expanded pulse, are we animating in response? */
+    private fun isPulseExpandingAnimated(): Flow<AnimatedValue<Boolean>> {
+        return notificationsKeyguardInteractor.isPulseExpanding
+            .pairwise(initialValue = null)
+            // If pulsing changes, start animating, unless it's the first emission
+            .map { (prev, expanding) ->
+                AnimatableEvent(expanding!!, startAnimating = prev != null)
+            }
+            .toAnimatedValueFlow(completionEvents = onVisAnimationComplete)
+    }
+
+    /** Are notifications completely hidden from view, are we animating in response? */
+    private fun areNotifsFullyHiddenAnimated(): Flow<AnimatedValue<Boolean>> {
+        return notificationsKeyguardInteractor.areNotificationsFullyHidden
+            .pairwise(initialValue = null)
+            .sample(deviceEntryInteractor.isBypassEnabled) { (prev, fullyHidden), bypassEnabled ->
+                val animate =
+                    when {
+                        // Don't animate for the first value
+                        prev == null -> false
+                        // Always animate if bypass is enabled.
+                        bypassEnabled -> true
+                        // If we're not bypassing and we're not going to AOD, then we're not
+                        // animating.
+                        !dozeParameters.alwaysOn -> false
+                        // Don't animate when going to AOD if the display needs blanking.
+                        dozeParameters.displayNeedsBlanking -> false
+                        // We only want the appear animations to happen when the notifications
+                        // get fully hidden, since otherwise the un-hide animation overlaps.
+                        featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> true
+                        else -> fullyHidden!!
+                    }
+                AnimatableEvent(fullyHidden!!, animate)
+            }
+            .toAnimatedValueFlow(completionEvents = onVisAnimationComplete)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
index 49f262d..c44a2b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
@@ -15,12 +15,18 @@
  */
 package com.android.systemui.statusbar.notification.icon.ui.viewmodel
 
+import com.android.systemui.util.ui.AnimatedValue
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.flowOf
 
 /** View-model for the overflow row of notification icons displayed in the notification shade. */
 class NotificationIconContainerShelfViewModel @Inject constructor() :
     NotificationIconContainerViewModel {
     override val animationsEnabled: Flow<Boolean> = flowOf(true)
+    override val isDozing: Flow<AnimatedValue<Boolean>> = emptyFlow()
+    override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow()
+    override fun completeDozeAnimation() {}
+    override fun completeVisibilityAnimation() {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
index ee57b78..035687a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -17,9 +17,11 @@
 
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.util.ui.AnimatedValue
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
 
 /** View-model for the row of notification icons displayed in the status bar, */
 class NotificationIconContainerStatusBarViewModel
@@ -35,4 +37,9 @@
         ) { panelTouchesEnabled, isKeyguardShowing ->
             panelTouchesEnabled && !isKeyguardShowing
         }
+
+    override val isDozing: Flow<AnimatedValue<Boolean>> = emptyFlow()
+    override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow()
+    override fun completeDozeAnimation() {}
+    override fun completeVisibilityAnimation() {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
index 6f8ce4b..65eb220 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.statusbar.notification.icon.ui.viewmodel
 
+import com.android.systemui.util.ui.AnimatedValue
 import kotlinx.coroutines.flow.Flow
 
 /**
@@ -24,4 +25,22 @@
 interface NotificationIconContainerViewModel {
     /** Are changes to the icon container animated? */
     val animationsEnabled: Flow<Boolean>
+
+    /** Should icons be rendered in "dozing" mode? */
+    val isDozing: Flow<AnimatedValue<Boolean>>
+
+    /** Is the icon container visible? */
+    val isVisible: Flow<AnimatedValue<Boolean>>
+
+    /**
+     * Signal completion of the [isDozing] animation; if [isDozing]'s [AnimatedValue.isAnimating]
+     * property was `true`, calling this method will update it to `false.
+     */
+    fun completeDozeAnimation()
+
+    /**
+     * Signal completion of the [isVisible] animation; if [isVisible]'s [AnimatedValue.isAnimating]
+     * property was `true`, calling this method will update it to `false.
+     */
+    fun completeVisibilityAnimation()
 }
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/notification/logging/NotificationMemoryDumper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt
index 197ae1a..dc9028d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.dump.DumpsysTableLogger
 import com.android.systemui.dump.Row
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import dalvik.annotation.optimization.NeverCompile
 import java.io.PrintWriter
 import javax.inject.Inject
 
@@ -39,6 +40,7 @@
         Log.i("NotificationMemory", "Registered dumpable.")
     }
 
+    @NeverCompile
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         val memoryUse =
             NotificationMemoryMeter.notificationMemoryUse(notificationPipeline.allNotifs)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 847d948..661768d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -351,12 +351,21 @@
 
     @Override
     public long performRemoveAnimation(long duration, long delay, float translationDirection,
-            boolean isHeadsUpAnimation, Runnable onFinishedRunnable,
+            boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener) {
         enableAppearDrawing(true);
         mIsHeadsUpAnimation = isHeadsUpAnimation;
-        startAppearAnimation(false /* isAppearing */, translationDirection,
-                delay, duration, onFinishedRunnable, animationListener);
+        if (mDrawingAppearAnimation) {
+            startAppearAnimation(false /* isAppearing */, translationDirection,
+                    delay, duration, onStartedRunnable, onFinishedRunnable, animationListener);
+        } else {
+            if (onStartedRunnable != null) {
+                onStartedRunnable.run();
+            }
+            if (onFinishedRunnable != null) {
+                onFinishedRunnable.run();
+            }
+        }
         return 0;
     }
 
@@ -365,12 +374,14 @@
             Runnable onFinishRunnable) {
         enableAppearDrawing(true);
         mIsHeadsUpAnimation = isHeadsUpAppear;
-        startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay,
-                duration, null, null);
+        if (mDrawingAppearAnimation) {
+            startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay,
+                    duration, null, null, null);
+        }
     }
 
     private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay,
-            long duration, final Runnable onFinishedRunnable,
+            long duration, final Runnable onStartedRunnable, final Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener) {
         mAnimationTranslationY = translationDirection * getActualHeight();
         cancelAppearAnimation();
@@ -434,6 +445,9 @@
 
             @Override
             public void onAnimationStart(Animator animation) {
+                if (onStartedRunnable != null) {
+                    onStartedRunnable.run();
+                }
                 mRunWithoutInterruptions = true;
                 Configuration.Builder builder = Configuration.Builder
                         .withView(getCujType(isAppearing), ActivatableNotificationView.this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 061132f..11c65e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -27,7 +27,6 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.app.Notification;
-import android.app.NotificationChannel;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -40,8 +39,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Trace;
-import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.IndentingPrintWriter;
@@ -73,15 +70,15 @@
 import com.android.internal.util.ContrastColorUtil;
 import com.android.internal.widget.CachingIconView;
 import com.android.internal.widget.CallLayout;
-import com.android.systemui.res.R;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.ViewRefactorFlag;
+import com.android.systemui.flags.RefactorFlag;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.StatusBarIconView;
@@ -274,8 +271,8 @@
     private OnExpandClickListener mOnExpandClickListener;
     private View.OnClickListener mOnFeedbackClickListener;
     private Path mExpandingClipPath;
-    private final ViewRefactorFlag mInlineReplyAnimation =
-            new ViewRefactorFlag(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
+    private final RefactorFlag mInlineReplyAnimation =
+            RefactorFlag.forView(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
 
     // Listener will be called when receiving a long click event.
     // Use #setLongPressPosition to optionally assign positional data with the long press.
@@ -1904,16 +1901,7 @@
             return traceTag;
         }
 
-        if (isSummaryWithChildren()) {
-            return traceTag + "(summary)";
-        }
-        Class<? extends Notification.Style> style =
-                getEntry().getSbn().getNotification().getNotificationStyle();
-        if (style == null) {
-            return traceTag + "(nostyle)";
-        } else {
-            return traceTag + "(" + style.getSimpleName() + ")";
-        }
+        return  traceTag + "(" + getEntry().getNotificationStyle() + ")";
     }
 
     @Override
@@ -2930,6 +2918,7 @@
             long delay,
             float translationDirection,
             boolean isHeadsUpAnimation,
+            Runnable onStartedRunnable,
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener) {
         if (mMenuRow != null && mMenuRow.isMenuVisible()) {
@@ -2937,10 +2926,16 @@
             if (anim != null) {
                 anim.addListener(new AnimatorListenerAdapter() {
                     @Override
+                    public void onAnimationStart(Animator animation) {
+                        if (onStartedRunnable != null) {
+                            onStartedRunnable.run();
+                        }
+                    }
+                    @Override
                     public void onAnimationEnd(Animator animation) {
                         ExpandableNotificationRow.super.performRemoveAnimation(
                                 duration, delay, translationDirection, isHeadsUpAnimation,
-                                onFinishedRunnable, animationListener);
+                                null, onFinishedRunnable, animationListener);
                     }
                 });
                 anim.start();
@@ -2948,7 +2943,7 @@
             }
         }
         return super.performRemoveAnimation(duration, delay, translationDirection,
-                isHeadsUpAnimation, onFinishedRunnable, animationListener);
+                isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 2599231..2a3e69b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -28,9 +28,9 @@
 import android.view.View;
 import android.view.ViewOutlineProvider;
 
-import com.android.systemui.res.R;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.ViewRefactorFlag;
+import com.android.systemui.flags.RefactorFlag;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.RoundableState;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
 import com.android.systemui.util.DumpUtilsKt;
@@ -49,8 +49,8 @@
     private float mOutlineAlpha = -1f;
     private boolean mAlwaysRoundBothCorners;
     private Path mTmpPath = new Path();
-    protected final ViewRefactorFlag mImprovedHunAnimation =
-            new ViewRefactorFlag(Flags.IMPROVED_HUN_ANIMATIONS);
+    protected final RefactorFlag mImprovedHunAnimation =
+            RefactorFlag.forView(Flags.IMPROVED_HUN_ANIMATIONS);
 
     /**
      * {@code false} if the children views of the {@link ExpandableOutlineView} are translated when
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index f2f55a8..6edab4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -69,6 +69,9 @@
     private boolean mClipToActualHeight = true;
     private boolean mChangingPosition = false;
     private ViewGroup mTransientContainer;
+
+    // Needs to be added as transient view when removed from parent, because it's in animation
+    private boolean mInRemovalAnimation;
     private boolean mInShelf;
     private boolean mTransformingInShelf;
     protected float mContentTransformationAmount;
@@ -381,6 +384,7 @@
      */
     public abstract long performRemoveAnimation(long duration,
             long delay, float translationDirection, boolean isHeadsUpAnimation,
+            Runnable onStartedRunnable,
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener);
 
@@ -604,6 +608,25 @@
     }
 
     /**
+     * Add the view to a transient container.
+     */
+    public void addToTransientContainer(ViewGroup container, int index) {
+        container.addTransientView(this, index);
+        setTransientContainer(container);
+    }
+
+    /**
+     * @return If the view is in a process of removal animation.
+     */
+    public boolean inRemovalAnimation() {
+        return mInRemovalAnimation;
+    }
+
+    public void setInRemovalAnimation(boolean inRemovalAnimation) {
+        mInRemovalAnimation = inRemovalAnimation;
+    }
+
+    /**
      * @return true if the group's expansion state is changing, false otherwise.
      */
     public boolean isGroupExpansionChanging() {
@@ -837,6 +860,7 @@
                 pw.println();
             }
             if (DUMP_VERBOSE) {
+                pw.println("mInRemovalAnimation: " + mInRemovalAnimation);
                 pw.println("mClipTopAmount: " + mClipTopAmount);
                 pw.println("mClipBottomAmount " + mClipBottomAmount);
                 pw.println("mClipToActualHeight: " + mClipToActualHeight);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index a27a305..60e75ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1363,12 +1363,12 @@
                         result.mController.setPendingIntent(existingPendingIntent);
                     }
                     if (result.mController.updatePendingIntentFromActions(actions)) {
-                        if (!result.mView.isActive()) {
-                            result.mView.focus();
+                        if (!result.mController.isActive()) {
+                            result.mController.focus();
                         }
                     } else {
-                        if (result.mView.isActive()) {
-                            result.mView.close();
+                        if (result.mController.isActive()) {
+                            result.mController.close();
                         }
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 0c686be..aabf295 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -167,7 +167,7 @@
     }
 
     @VisibleForTesting
-    boolean isSecondaryVisible() {
+    public boolean isSecondaryVisible() {
         return mIsSecondaryVisible;
     }
 
@@ -179,7 +179,8 @@
         return mIsVisible;
     }
 
-    void setDuration(int duration) {
+    @VisibleForTesting
+    public void setDuration(int duration) {
         mDuration = duration;
     }
 
@@ -236,9 +237,13 @@
     @Override
     public long performRemoveAnimation(long duration, long delay,
             float translationDirection, boolean isHeadsUpAnimation,
+            Runnable onStartedRunnable,
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener) {
         // TODO: Use duration
+        if (onStartedRunnable != null) {
+            onStartedRunnable.run();
+        }
         setContentVisible(false, true /* animate */, (cancelled) -> onFinishedRunnable.run());
         return 0;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
index 5d46f52..bae5baa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
@@ -25,9 +25,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.notification.row.ExpandableView
 
-/**
- * Root view to insert Lock screen media controls into the notification stack.
- */
+/** Root view to insert Lock screen media controls into the notification stack. */
 class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableView(context, attrs) {
 
     override var clipHeight = 0
@@ -46,8 +44,8 @@
     }
 
     private fun updateResources() {
-        cornerRadius = context.resources
-                .getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat()
+        cornerRadius =
+            context.resources.getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat()
     }
 
     public override fun updateClipping() {
@@ -70,18 +68,23 @@
     }
 
     override fun performRemoveAnimation(
-            duration: Long,
-            delay: Long,
-            translationDirection: Float,
-            isHeadsUpAnimation: Boolean,
-            onFinishedRunnable: Runnable?,
-            animationListener: AnimatorListenerAdapter?
+        duration: Long,
+        delay: Long,
+        translationDirection: Float,
+        isHeadsUpAnimation: Boolean,
+        onStartedRunnable: Runnable?,
+        onFinishedRunnable: Runnable?,
+        animationListener: AnimatorListenerAdapter?
     ): Long {
         return 0
     }
 
-    override fun performAddAnimation(delay: Long, duration: Long, isHeadsUpAppear: Boolean,
-                                     onEnd: Runnable?) {
+    override fun performAddAnimation(
+        delay: Long,
+        duration: Long,
+        isHeadsUpAppear: Boolean,
+        onEnd: Runnable?
+    ) {
         // No animation, it doesn't need it, this would be local
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 78d7558..dba93d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -20,6 +20,7 @@
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL;
+import static com.android.systemui.flags.Flags.UNCLEARED_TRANSIENT_HUN_FIX;
 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
 import static com.android.systemui.util.DumpUtilsKt.println;
@@ -86,12 +87,12 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.ExpandHelper;
-import com.android.systemui.res.R;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.ViewRefactorFlag;
+import com.android.systemui.flags.RefactorFlag;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.res.R;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.statusbar.CommandQueue;
@@ -106,12 +107,12 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.row.FooterView;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
@@ -200,8 +201,8 @@
     private Set<Integer> mDebugTextUsedYPositions;
     private final boolean mDebugRemoveAnimation;
     private final boolean mSensitiveRevealAnimEndabled;
-    private final ViewRefactorFlag mAnimatedInsets;
-    private final ViewRefactorFlag mShelfRefactor;
+    private final RefactorFlag mAnimatedInsets;
+    private final RefactorFlag mShelfRefactor;
 
     private final boolean mNewAodTransition;
 
@@ -576,6 +577,7 @@
         mSplitShadeStateController = splitShadeStateController;
         updateSplitNotificationShade();
     }
+    private FeatureFlags mFeatureFlags;
 
     private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
             new ExpandableView.OnHeightChangedListener() {
@@ -628,16 +630,16 @@
     public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
         super(context, attrs, 0, 0);
         Resources res = getResources();
-        FeatureFlags featureFlags = Dependency.get(FeatureFlags.class);
-        mIsSmallLandscapeLockscreenEnabled = featureFlags.isEnabled(
+        mFeatureFlags = Dependency.get(FeatureFlags.class);
+        mIsSmallLandscapeLockscreenEnabled = mFeatureFlags.isEnabled(
                 Flags.LOCKSCREEN_ENABLE_LANDSCAPE);
-        mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
-        mNewAodTransition = featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION);
-        mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
-        mSensitiveRevealAnimEndabled = featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
+        mDebugLines = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
+        mNewAodTransition = mFeatureFlags.isEnabled(Flags.NEW_AOD_TRANSITION);
+        mDebugRemoveAnimation = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
+        mSensitiveRevealAnimEndabled = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
         mAnimatedInsets =
-                new ViewRefactorFlag(featureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
-        mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR);
+                new RefactorFlag(mFeatureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
+        mShelfRefactor = new RefactorFlag(mFeatureFlags, Flags.NOTIFICATION_SHELF_REFACTOR);
         mSectionsManager = Dependency.get(NotificationSectionsManager.class);
         mScreenOffAnimationController =
                 Dependency.get(ScreenOffAnimationController.class);
@@ -2733,7 +2735,7 @@
      * @param listener callback for notification removed
      */
     public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) {
-        mShelfRefactor.assertDisabled();
+        mShelfRefactor.assertInLegacyMode();
         mOnNotificationRemovedListener = listener;
     }
 
@@ -2779,8 +2781,7 @@
         if (animationGenerated) {
             if (!mSwipedOutViews.contains(child) || !isFullySwipedOut(child)) {
                 logAddTransientChild(child, container);
-                container.addTransientView(child, 0);
-                child.setTransientContainer(container);
+                child.addToTransientContainer(container, 0);
             }
         } else {
             mSwipedOutViews.remove(child);
@@ -2870,7 +2871,8 @@
      * Generate a remove animation for a child view.
      *
      * @param child The view to generate the remove animation for.
-     * @return Whether an animation was generated.
+     * @return Whether a new animation was generated or an existing animation was detected by this
+     * method. We need this to determine if a transient view is needed.
      */
     boolean generateRemoveAnimation(ExpandableView child) {
         String key = "";
@@ -2887,10 +2889,23 @@
             mAddedHeadsUpChildren.remove(child);
             return false;
         }
-        if (isClickedHeadsUp(child)) {
-            // An animation is already running, add it transiently
-            mClearTransientViewsWhenFinished.add(child);
-            return true;
+        if (mFeatureFlags.isEnabled(UNCLEARED_TRANSIENT_HUN_FIX)) {
+            // Skip adding animation for clicked heads up notifications when the
+            // Shade is closed, because the animation event is generated in
+            // generateHeadsUpAnimationEvents. Only report that an animation was
+            // actually generated (thus requesting the transient view be added)
+            // if a removal animation is in progress.
+            if (!isExpanded() && isClickedHeadsUp(child)) {
+                // An animation is already running, add it transiently
+                mClearTransientViewsWhenFinished.add(child);
+                return child.inRemovalAnimation();
+            }
+        } else {
+            if (isClickedHeadsUp(child)) {
+                // An animation is already running, add it transiently
+                mClearTransientViewsWhenFinished.add(child);
+                return true;
+            }
         }
         if (mDebugRemoveAnimation) {
             Log.d(TAG, "generateRemove " + key
@@ -4967,12 +4982,12 @@
 
     @Nullable
     public ExpandableView getShelf() {
-        if (!mShelfRefactor.expectEnabled()) return null;
+        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return null;
         return mShelf;
     }
 
     public void setShelf(NotificationShelf shelf) {
-        if (!mShelfRefactor.expectEnabled()) return;
+        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
         int index = -1;
         if (mShelf != null) {
             index = indexOfChild(mShelf);
@@ -4986,7 +5001,7 @@
     }
 
     public void setShelfController(NotificationShelfController notificationShelfController) {
-        mShelfRefactor.assertDisabled();
+        mShelfRefactor.assertInLegacyMode();
         int index = -1;
         if (mShelf != null) {
             index = indexOfChild(mShelf);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 9695cb1..6a70815 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -64,7 +64,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.ViewRefactorFlag;
+import com.android.systemui.flags.RefactorFlag;
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -207,7 +207,7 @@
     private boolean mIsInTransitionToAod = false;
 
     private final FeatureFlags mFeatureFlags;
-    private final ViewRefactorFlag mShelfRefactor;
+    private final RefactorFlag mShelfRefactor;
     private final NotificationTargetsHelper mNotificationTargetsHelper;
     private final SecureSettings mSecureSettings;
     private final NotificationDismissibilityProvider mDismissibilityProvider;
@@ -719,7 +719,7 @@
         mShadeController = shadeController;
         mNotifIconAreaController = notifIconAreaController;
         mFeatureFlags = featureFlags;
-        mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR);
+        mShelfRefactor = new RefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR);
         mNotificationTargetsHelper = notificationTargetsHelper;
         mSecureSettings = secureSettings;
         mDismissibilityProvider = dismissibilityProvider;
@@ -1431,7 +1431,7 @@
     }
 
     public void setShelfController(NotificationShelfController notificationShelfController) {
-        mShelfRefactor.assertDisabled();
+        mShelfRefactor.assertInLegacyMode();
         mView.setShelfController(notificationShelfController);
     }
 
@@ -1644,12 +1644,12 @@
     }
 
     public void setShelf(NotificationShelf shelf) {
-        if (!mShelfRefactor.expectEnabled()) return;
+        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
         mView.setShelf(shelf);
     }
 
     public int getShelfHeight() {
-        if (!mShelfRefactor.expectEnabled()) {
+        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) {
             return 0;
         }
         ExpandableView shelf = mView.getShelf();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 8ca1852..80f98a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -27,16 +27,16 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.res.R;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.SourceType;
+import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.row.FooterView;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 69453c6..e94258f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
@@ -346,21 +349,19 @@
             ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents) {
         boolean needsCustomAnimation = false;
         for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
-            final ExpandableView changingView = (ExpandableView) event.mChangingView;
+            final ExpandableView changingView = event.mChangingView;
             boolean loggable = false;
             boolean isHeadsUp = false;
-            boolean isGroupChild = false;
             String key = null;
             if (changingView instanceof ExpandableNotificationRow && mLogger != null) {
                 loggable = true;
                 isHeadsUp = ((ExpandableNotificationRow) changingView).isHeadsUp();
-                isGroupChild = changingView.isChildInGroup();
                 key = ((ExpandableNotificationRow) changingView).getEntry().getKey();
             }
             if (event.animationType ==
                     NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
 
-                // This item is added, initialize it's properties.
+                // This item is added, initialize its properties.
                 ExpandableViewState viewState = changingView.getViewState();
                 if (viewState == null || viewState.gone) {
                     // The position for this child was never generated, let's continue.
@@ -374,7 +375,11 @@
 
             } else if (event.animationType ==
                     NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
-                if (changingView.getVisibility() != View.VISIBLE) {
+                int changingViewVisibility = changingView.getVisibility();
+                if (loggable) {
+                    mLogger.processAnimationEventsRemoval(key, changingViewVisibility, isHeadsUp);
+                }
+                if (changingViewVisibility != View.VISIBLE) {
                     changingView.removeFromTransientContainer();
                     continue;
                 }
@@ -410,30 +415,40 @@
                     translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
 
                 }
-                Runnable postAnimation = changingView::removeFromTransientContainer;
+                Runnable postAnimation;
+                Runnable startAnimation;
                 if (loggable) {
                     String finalKey = key;
-                    if (isHeadsUp) {
-                        mLogger.logHUNViewDisappearingWithRemoveEvent(key);
-                        postAnimation = () -> {
-                            mLogger.disappearAnimationEnded(finalKey);
-                            changingView.removeFromTransientContainer();
-                        };
-                    } else if (isGroupChild) {
-                        mLogger.groupChildRemovalEventProcessed(key);
-                        postAnimation = () -> {
-                            mLogger.groupChildRemovalAnimationEnded(finalKey);
-                            changingView.removeFromTransientContainer();
-                        };
-                    }
+                    final boolean finalIsHeadsHp = isHeadsUp;
+                    startAnimation = () -> {
+                        mLogger.animationStart(finalKey, "ANIMATION_TYPE_REMOVE", finalIsHeadsHp);
+                        changingView.setInRemovalAnimation(true);
+                    };
+                    postAnimation = () -> {
+                        mLogger.animationEnd(finalKey, "ANIMATION_TYPE_REMOVE", finalIsHeadsHp);
+                        changingView.setInRemovalAnimation(false);
+                        changingView.removeFromTransientContainer();
+                    };
+                } else {
+                    startAnimation = ()-> {
+                        changingView.setInRemovalAnimation(true);
+                    };
+                    postAnimation = () -> {
+                        changingView.setInRemovalAnimation(false);
+                        changingView.removeFromTransientContainer();
+                    };
                 }
                 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
                         0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
-                        postAnimation, getGlobalAnimationFinishedListener());
+                        startAnimation, postAnimation, getGlobalAnimationFinishedListener());
                 needsCustomAnimation = true;
             } else if (event.animationType ==
                 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
-                if (mHostLayout.isFullySwipedOut(changingView)) {
+                boolean isFullySwipedOut = mHostLayout.isFullySwipedOut(changingView);
+                if (loggable) {
+                    mLogger.processAnimationEventsRemoveSwipeOut(key, isFullySwipedOut, isHeadsUp);
+                }
+                if (isFullySwipedOut) {
                     changingView.removeFromTransientContainer();
                 }
             } else if (event.animationType == NotificationStackScrollLayout
@@ -442,7 +457,7 @@
                 row.prepareExpansionChanged();
             } else if (event.animationType == NotificationStackScrollLayout
                     .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) {
-                // This item is added, initialize it's properties.
+                // This item is added, initialize its properties.
                 ExpandableViewState viewState = changingView.getViewState();
                 mTmpState.copyFrom(viewState);
                 if (event.headsUpFromBottom) {
@@ -464,22 +479,23 @@
                 }
 
                 mTmpState.applyToView(changingView);
-            } else if (event.animationType == NotificationStackScrollLayout
-                            .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR ||
-                    event.animationType == NotificationStackScrollLayout
-                            .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
+            } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR
+                    || event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
                 mHeadsUpDisappearChildren.add(changingView);
                 Runnable endRunnable = null;
                 if (changingView.getParent() == null) {
-                    // This notification was actually removed, so we need to add it transiently
+                    // This notification was actually removed, so we need to add it
+                    // transiently
                     mHostLayout.addTransientView(changingView, 0);
                     changingView.setTransientContainer(mHostLayout);
                     mTmpState.initFrom(changingView);
                     endRunnable = changingView::removeFromTransientContainer;
                 }
+
                 boolean needsAnimation = true;
                 if (changingView instanceof ExpandableNotificationRow) {
-                    ExpandableNotificationRow row = (ExpandableNotificationRow) changingView;
+                    ExpandableNotificationRow row =
+                            (ExpandableNotificationRow) changingView;
                     if (row.isDismissed()) {
                         needsAnimation = false;
                     }
@@ -488,21 +504,43 @@
                     // We need to add the global animation listener, since once no animations are
                     // running anymore, the panel will instantly hide itself. We need to wait until
                     // the animation is fully finished for this though.
-                    Runnable postAnimation = endRunnable;
+                    final Runnable tmpEndRunnable = endRunnable;
+                    Runnable postAnimation;
+                    Runnable startAnimation;
                     if (loggable) {
-                        mLogger.logHUNViewDisappearing(key);
-
-                        Runnable finalEndRunnable = endRunnable;
                         String finalKey1 = key;
+                        final boolean finalIsHeadsUp = isHeadsUp;
+                        final String type =
+                                event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR
+                                        ? "ANIMATION_TYPE_HEADS_UP_DISAPPEAR"
+                                        : "ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK";
+                        startAnimation = () -> {
+                            mLogger.animationStart(finalKey1, type, finalIsHeadsUp);
+                            changingView.setInRemovalAnimation(true);
+                        };
                         postAnimation = () -> {
-                            mLogger.disappearAnimationEnded(finalKey1);
-                            if (finalEndRunnable != null) finalEndRunnable.run();
+                            mLogger.animationEnd(finalKey1, type, finalIsHeadsUp);
+                            changingView.setInRemovalAnimation(false);
+                            if (tmpEndRunnable != null) {
+                                tmpEndRunnable.run();
+                            }
+                        };
+                    } else {
+                        postAnimation = () -> {
+                            changingView.setInRemovalAnimation(false);
+                            if (tmpEndRunnable != null) {
+                                tmpEndRunnable.run();
+                            }
+                        };
+                        startAnimation = () -> {
+                            changingView.setInRemovalAnimation(true);
                         };
                     }
                     long removeAnimationDelay = changingView.performRemoveAnimation(
                             ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
                             0, 0.0f, true /* isHeadsUpAppear */,
-                            postAnimation, getGlobalAnimationFinishedListener());
+                            startAnimation, postAnimation,
+                            getGlobalAnimationFinishedListener());
                     mAnimationProperties.delay += removeAnimationDelay;
                 } else if (endRunnable != null) {
                     endRunnable.run();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
index 0b2c486..d635f89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -5,74 +5,104 @@
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.log.dagger.NotificationRenderLog
 import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.util.visibilityString
 import javax.inject.Inject
 
-class StackStateLogger @Inject constructor(
+class StackStateLogger
+@Inject
+constructor(
     @NotificationHeadsUpLog private val buffer: LogBuffer,
     @NotificationRenderLog private val notificationRenderBuffer: LogBuffer
 ) {
-    fun logHUNViewDisappearing(key: String) {
-        buffer.log(TAG, LogLevel.INFO, {
-            str1 = logKey(key)
-        }, {
-            "Heads up view disappearing $str1 "
-        })
-    }
 
     fun logHUNViewAppearing(key: String) {
-        buffer.log(TAG, LogLevel.INFO, {
-            str1 = logKey(key)
-        }, {
-            "Heads up notification view appearing $str1 "
-        })
-    }
-
-    fun logHUNViewDisappearingWithRemoveEvent(key: String) {
-        buffer.log(TAG, LogLevel.ERROR, {
-            str1 = logKey(key)
-        }, {
-            "Heads up view disappearing $str1 for ANIMATION_TYPE_REMOVE"
-        })
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            { str1 = logKey(key) },
+            { "Heads up notification view appearing $str1 " }
+        )
     }
 
     fun logHUNViewAppearingWithAddEvent(key: String) {
-        buffer.log(TAG, LogLevel.ERROR, {
-            str1 = logKey(key)
-        }, {
-            "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD"
-        })
-    }
-
-    fun disappearAnimationEnded(key: String) {
-        buffer.log(TAG, LogLevel.INFO, {
-            str1 = logKey(key)
-        }, {
-            "Heads up notification disappear animation ended $str1 "
-        })
+        buffer.log(
+            TAG,
+            LogLevel.ERROR,
+            { str1 = logKey(key) },
+            { "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD" }
+        )
     }
 
     fun appearAnimationEnded(key: String) {
-        buffer.log(TAG, LogLevel.INFO, {
-            str1 = logKey(key)
-        }, {
-            "Heads up notification appear animation ended $str1 "
-        })
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            { str1 = logKey(key) },
+            { "Heads up notification appear animation ended $str1 " }
+        )
     }
 
-    fun groupChildRemovalEventProcessed(key: String) {
-        notificationRenderBuffer.log(TAG, LogLevel.DEBUG, {
-            str1 = logKey(key)
-        }, {
-            "Group Child Notification removal event processed $str1 for ANIMATION_TYPE_REMOVE"
-        })
+    fun processAnimationEventsRemoval(key: String, visibility: Int, isHeadsUp: Boolean) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                str1 = logKey(key)
+                int1 = visibility
+                bool1 = isHeadsUp
+            },
+            {
+                "ProcessAnimationEvents ANIMATION_TYPE_REMOVE for: $str1, " +
+                    "changingViewVisibility: ${visibilityString(int1)}, isHeadsUp: $bool1"
+            }
+        )
     }
-    fun groupChildRemovalAnimationEnded(key: String) {
-        notificationRenderBuffer.log(TAG, LogLevel.INFO, {
-            str1 = logKey(key)
-        }, {
-            "Group child notification removal animation ended $str1 "
-        })
+
+    fun processAnimationEventsRemoveSwipeOut(
+        key: String,
+        isFullySwipedOut: Boolean,
+        isHeadsUp: Boolean
+    ) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                str1 = logKey(key)
+                bool1 = isFullySwipedOut
+                bool2 = isHeadsUp
+            },
+            {
+                "ProcessAnimationEvents ANIMATION_TYPE_REMOVE_SWIPED_OUT for: $str1, " +
+                    "isFullySwipedOut: $bool1, isHeadsUp: $bool2"
+            }
+        )
+    }
+
+    fun animationStart(key: String?, animationType: String, isHeadsUp: Boolean) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                str1 = logKey(key)
+                str2 = animationType
+                bool1 = isHeadsUp
+            },
+            { "Animation Start, type: $str2, notif key: $str1, isHeadsUp: $bool1" }
+        )
+    }
+
+    fun animationEnd(key: String, animationType: String, isHeadsUp: Boolean) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                str1 = logKey(key)
+                str2 = animationType
+                bool1 = isHeadsUp
+            },
+            { "Animation End, type: $str2, notif key: $str1, isHeadsUp: $bool1" }
+        )
     }
 }
 
-private const val TAG = "StackScroll"
\ No newline at end of file
+private const val TAG = "StackScroll"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 697d297..3877bb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -28,15 +28,15 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.NightDisplayListenerModule;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.AutoAddTracker;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.ReduceBrightColorsController;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 import com.android.systemui.qs.external.CustomTile;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
 import com.android.systemui.statusbar.policy.DataSaverController;
@@ -461,8 +461,8 @@
     };
 
     @VisibleForTesting
-    protected SettingObserver getSecureSettingForKey(String key) {
-        for (SettingObserver s : mAutoAddSettingList) {
+    protected UserSettingObserver getSecureSettingForKey(String key) {
+        for (UserSettingObserver s : mAutoAddSettingList) {
             if (Objects.equals(key, s.getKey())) {
                 return s;
             }
@@ -476,7 +476,7 @@
      * When the setting changes to a value different from 0, if the tile has not been auto added
      * before, it will be added and the listener will be stopped.
      */
-    private class AutoAddSetting extends SettingObserver {
+    private class AutoAddSetting extends UserSettingObserver {
         private final String mSpec;
 
         AutoAddSetting(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 6e6318e..9fb6c1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -239,6 +239,8 @@
 
 import dagger.Lazy;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.List;
@@ -310,8 +312,8 @@
             };
 
     void onStatusBarWindowStateChanged(@WindowVisibleState int state) {
-        updateBubblesVisibility();
         mStatusBarWindowState = state;
+        updateBubblesVisibility();
     }
 
     @Override
@@ -1086,6 +1088,7 @@
      * @deprecated use {@link
      * WindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible} instead.
      */    @VisibleForTesting
+    @Deprecated
     void initShadeVisibilityListener() {
         mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() {
             @Override
@@ -1723,7 +1726,8 @@
         StatusBarMode mode = mStatusBarModeRepository.getStatusBarMode().getValue();
         mBubblesOptional.ifPresent(bubbles -> bubbles.onStatusBarVisibilityChanged(
                 mode != StatusBarMode.LIGHTS_OUT
-                        && mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT));
+                        && mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT
+                        && mStatusBarWindowState != WINDOW_STATE_HIDDEN));
     }
 
     void checkBarMode(
@@ -1763,6 +1767,7 @@
         }
     }
 
+    @NeverCompile
     @Override
     public void dump(PrintWriter pwOriginal, String[] args) {
         IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
@@ -2599,7 +2604,10 @@
                         mShouldDelayWakeUpAnimation);
 
                 updateIsKeyguard();
+                // TODO(b/301913237): can't delay transition if config_displayBlanksAfterDoze=true,
+                // otherwise, the clock will flicker during LOCKSCREEN_TRANSITION_FROM_AOD
                 mShouldDelayLockscreenTransitionFromAod = mDozeParameters.getAlwaysOn()
+                        && !mDozeParameters.getDisplayNeedsBlanking()
                         && mFeatureFlags.isEnabled(
                                 Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD);
                 if (!mShouldDelayLockscreenTransitionFromAod) {
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/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index 1b9e5b3..f9856b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -36,16 +36,16 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.settingslib.Utils;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.ViewRefactorFlag;
+import com.android.systemui.flags.RefactorFlag;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -109,7 +109,7 @@
     private final ArrayList<Rect> mTintAreas = new ArrayList<>();
     private final Context mContext;
 
-    private final ViewRefactorFlag mShelfRefactor;
+    private final RefactorFlag mShelfRefactor;
 
     private final boolean mNewAodTransition;
 
@@ -150,7 +150,7 @@
         mContrastColorUtil = ContrastColorUtil.getInstance(context);
         mContext = context;
         mStatusBarStateController = statusBarStateController;
-        mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR);
+        mShelfRefactor = new RefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR);
         mNewAodTransition = featureFlags.isEnabled(NEW_AOD_TRANSITION);
         mStatusBarStateController.addCallback(this);
         mMediaManager = notificationMediaManager;
@@ -205,14 +205,13 @@
     }
 
     public void setupShelf(NotificationShelfController notificationShelfController) {
-        mShelfRefactor.assertDisabled();
+        mShelfRefactor.assertInLegacyMode();
         mShelfIcons = notificationShelfController.getShelfIcons();
     }
 
     public void setShelfIcons(NotificationIconContainer icons) {
-        if (mShelfRefactor.expectEnabled()) {
-            mShelfIcons = icons;
-        }
+        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
+        mShelfIcons = icons;
     }
 
     public void onDensityOrFontScaleChanged(@NotNull Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 7cbaf63..b15c0fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -33,6 +33,7 @@
 import android.view.ViewGroup;
 import android.view.animation.Interpolator;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.collection.ArrayMap;
 
@@ -624,12 +625,32 @@
     }
 
     public void setDozing(boolean dozing, boolean fade, long delay) {
+        setDozing(dozing, fade, delay, /* endRunnable= */ null);
+    }
+
+    public void setDozing(boolean dozing, boolean fade, long delay,
+            @Nullable Runnable endRunnable) {
         mDozing = dozing;
         mDisallowNextAnimation |= !fade;
-        for (int i = 0; i < getChildCount(); i++) {
+        final int childCount = getChildCount();
+        // Track all the child invocations of setDozing, invoking the top-level endRunnable once
+        // they have all completed.
+        final Runnable onChildCompleted = endRunnable == null ? null : new Runnable() {
+            private int mPendingCallbacks = childCount;
+
+            @Override
+            public void run() {
+                if (--mPendingCallbacks == 0) {
+                    endRunnable.run();
+                }
+            }
+        };
+        for (int i = 0; i < childCount; i++) {
             View view = getChildAt(i);
             if (view instanceof StatusBarIconView) {
-                ((StatusBarIconView) view).setDozing(dozing, fade, delay);
+                ((StatusBarIconView) view).setDozing(dozing, fade, delay, onChildCompleted);
+            } else if (onChildCompleted != null) {
+                onChildCompleted.run();
             }
         }
     }
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/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 62b2445..3adf338 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -296,7 +296,6 @@
 
     final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
     private boolean mIsBackAnimationEnabled;
-    private final boolean mUdfpsNewTouchDetectionEnabled;
     private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
     private final ActivityStarter mActivityStarter;
 
@@ -398,7 +397,6 @@
         mAlternateBouncerInteractor = alternateBouncerInteractor;
         mIsBackAnimationEnabled =
                 featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM);
-        mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION);
         mUdfpsOverlayInteractor = udfpsOverlayInteractor;
         mActivityStarter = activityStarter;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
@@ -1594,7 +1592,7 @@
             final boolean actionDownThenUp = mAlternateBouncerInteractor.getReceivedDownTouch()
                     && event.getActionMasked() == MotionEvent.ACTION_UP;
             final boolean udfpsOverlayWillForwardEventsOutsideNotificationShade =
-                    mUdfpsNewTouchDetectionEnabled && mKeyguardUpdateManager.isUdfpsEnrolled();
+                    mKeyguardUpdateManager.isUdfpsEnrolled();
             final boolean actionOutsideShouldDismissAlternateBouncer =
                     event.getActionMasked() == MotionEvent.ACTION_OUTSIDE
                     && !udfpsOverlayWillForwardEventsOutsideNotificationShade;
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/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 2c15e27..de37170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -34,6 +34,8 @@
 import com.android.systemui.util.TraceUtils
 import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 
 /**
  * When to show the keyguard (AOD) view. This should be once the light reveal scrim is barely
@@ -65,7 +67,8 @@
     private val notifShadeWindowControllerLazy: dagger.Lazy<NotificationShadeWindowController>,
     private val interactionJankMonitor: InteractionJankMonitor,
     private val powerManager: PowerManager,
-    private val handler: Handler = Handler()
+    private val handler: Handler = Handler(),
+    private val featureFlags: FeatureFlags,
 ) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
     private lateinit var centralSurfaces: CentralSurfaces
     private lateinit var shadeViewController: ShadeViewController
@@ -285,7 +288,11 @@
                 // up, with unpredictable consequences.
                 if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY) &&
                         shouldAnimateInKeyguard) {
-                    aodUiAnimationPlaying = true
+                    if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+                        // Tracking this state should no longer be relevant, as the isInteractive
+                        // check covers it
+                        aodUiAnimationPlaying = true
+                    }
 
                     // Show AOD. That'll cause the KeyguardVisibilityHelper to call
                     // #animateInKeyguard.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
index 8ff9198..8862c77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.pipeline.airplane.data.repository
 
 import android.os.Handler
-import android.os.UserHandle
 import android.provider.Settings.Global
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -66,13 +65,7 @@
     override val isAirplaneMode: StateFlow<Boolean> =
         conflatedCallbackFlow {
                 val observer =
-                    object :
-                        SettingObserver(
-                            globalSettings,
-                            bgHandler,
-                            Global.AIRPLANE_MODE_ON,
-                            UserHandle.USER_ALL
-                        ) {
+                    object : SettingObserver(globalSettings, bgHandler, Global.AIRPLANE_MODE_ON) {
                         override fun handleValueChanged(value: Int, observedChange: Boolean) {
                             trySend(value == 1)
                         }
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/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 945cc6b..53b343c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -27,6 +27,7 @@
 import android.os.UserManager;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.settingslib.bluetooth.BluetoothCallback;
@@ -36,6 +37,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
 import com.android.systemui.bluetooth.BluetoothLogger;
 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.settings.UserTracker;
@@ -81,6 +83,8 @@
     private int mState;
 
     private final BluetoothAdapter mAdapter;
+
+    private final Executor mBackgroundExecutor;
     /**
      */
     @Inject
@@ -90,6 +94,7 @@
             DumpManager dumpManager,
             BluetoothLogger logger,
             BluetoothRepository bluetoothRepository,
+            @Background Executor executor,
             @Main Looper mainLooper,
             @Nullable LocalBluetoothManager localBluetoothManager,
             @Nullable BluetoothAdapter bluetoothAdapter) {
@@ -98,6 +103,7 @@
         mBluetoothRepository = bluetoothRepository;
         mLocalBluetoothManager = localBluetoothManager;
         mHandler = new H(mainLooper);
+        mBackgroundExecutor = executor;
         if (mLocalBluetoothManager != null) {
             mLocalBluetoothManager.getEventManager().registerCallback(this);
             mLocalBluetoothManager.getProfileManager().addServiceListener(this);
@@ -218,6 +224,7 @@
         return mIsActive;
     }
 
+    @WorkerThread
     @Override
     public void setBluetoothEnabled(boolean enabled) {
         if (mLocalBluetoothManager != null) {
@@ -230,6 +237,7 @@
         return mLocalBluetoothManager != null;
     }
 
+    @WorkerThread
     @Override
     public String getConnectedDeviceName() {
         synchronized (mConnectedDevices) {
@@ -251,6 +259,7 @@
                 getDevices(), this::onConnectionStatusFetched);
     }
 
+    // Careful! This may be invoked in the main thread.
     private void onConnectionStatusFetched(ConnectionStatusModel status) {
         List<CachedBluetoothDevice> newList = status.getConnectedDevices();
         int state = status.getMaxConnectionState();
@@ -282,30 +291,33 @@
     }
 
     private void updateAudioProfile() {
-        boolean audioProfileConnected = false;
-        boolean otherProfileConnected = false;
+        // We want this in the background as calls inside `LocalBluetoothProfile` end up being
+        // binder calls
+        mBackgroundExecutor.execute(() -> {
+            boolean audioProfileConnected = false;
+            boolean otherProfileConnected = false;
 
-        for (CachedBluetoothDevice device : getDevices()) {
-            for (LocalBluetoothProfile profile : device.getProfiles()) {
-                int profileId = profile.getProfileId();
-                boolean isConnected = device.isConnectedProfile(profile);
-                if (profileId == BluetoothProfile.HEADSET
-                        || profileId == BluetoothProfile.A2DP
-                        || profileId == BluetoothProfile.HEARING_AID
-                        || profileId == BluetoothProfile.LE_AUDIO) {
-                    audioProfileConnected |= isConnected;
-                } else {
-                    otherProfileConnected |= isConnected;
+            for (CachedBluetoothDevice device : getDevices()) {
+                for (LocalBluetoothProfile profile : device.getProfiles()) {
+                    int profileId = profile.getProfileId();
+                    boolean isConnected = device.isConnectedProfile(profile);
+                    if (profileId == BluetoothProfile.HEADSET
+                            || profileId == BluetoothProfile.A2DP
+                            || profileId == BluetoothProfile.HEARING_AID
+                            || profileId == BluetoothProfile.LE_AUDIO) {
+                        audioProfileConnected |= isConnected;
+                    } else {
+                        otherProfileConnected |= isConnected;
+                    }
                 }
             }
-        }
 
-        boolean audioProfileOnly = (audioProfileConnected && !otherProfileConnected);
-        if (audioProfileOnly != mAudioProfileOnly) {
-            mAudioProfileOnly = audioProfileOnly;
-            mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
-        }
-
+            boolean audioProfileOnly = (audioProfileConnected && !otherProfileConnected);
+            if (audioProfileOnly != mAudioProfileOnly) {
+                mAudioProfileOnly = audioProfileOnly;
+                mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
+            }
+        });
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
new file mode 100644
index 0000000..21acfb4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.policy
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * A [Flow] that emits whenever screen density or font scale has changed.
+ *
+ * @see ConfigurationController.ConfigurationListener.onDensityOrFontScaleChanged
+ */
+val ConfigurationController.onDensityOrFontScaleChanged: Flow<Unit>
+    get() =
+        ConflatedCallbackFlow.conflatedCallbackFlow {
+            val listener =
+                object : ConfigurationController.ConfigurationListener {
+                    override fun onDensityOrFontScaleChanged() {
+                        trySend(Unit)
+                    }
+                }
+            addCallback(listener)
+            awaitClose { removeCallback(listener) }
+        }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
index 8c61ada..8b20283 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
@@ -62,7 +62,7 @@
     }
 
     private val deviceProvisionedUri = globalSettings.getUriFor(Settings.Global.DEVICE_PROVISIONED)
-    private val frpActiveUri = secureSettings.getUriFor(Settings.Secure.SECURE_FRP_MODE)
+    private val frpActiveUri = globalSettings.getUriFor(Settings.Global.SECURE_FRP_MODE)
     private val userSetupUri = secureSettings.getUriFor(Settings.Secure.USER_SETUP_COMPLETE)
 
     private val deviceProvisioned = AtomicBoolean(false)
@@ -148,7 +148,7 @@
                     .set(globalSettings.getInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0)
         }
         if (updateFrp) {
-            frpActive.set(globalSettings.getInt(Settings.Secure.SECURE_FRP_MODE, 0) != 0)
+            frpActive.set(globalSettings.getInt(Settings.Global.SECURE_FRP_MODE, 0) != 0)
         }
         synchronized(lock) {
             if (updateUser == ALL_USERS) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 62e2381..b614b6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -38,6 +38,7 @@
 import com.android.systemui.res.R;
 import com.android.systemui.animation.Expandable;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.user.UserSwitchDialogController;
@@ -148,6 +149,7 @@
             DozeParameters dozeParameters,
             ScreenOffAnimationController screenOffAnimationController,
             UserSwitchDialogController userSwitchDialogController,
+            FeatureFlags featureFlags,
             UiEventLogger uiEventLogger) {
         super(view);
         if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
@@ -160,7 +162,8 @@
         mStatusBarStateController = statusBarStateController;
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
                 keyguardStateController, dozeParameters,
-                screenOffAnimationController,  /* animateYPos= */ false, /* logBuffer= */ null);
+                screenOffAnimationController,  /* animateYPos= */ false,
+                featureFlags, /* logBuffer= */ null);
         mUserSwitchDialogController = userSwitchDialogController;
         mUiEventLogger = uiEventLogger;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index bb074ac..dfe2686 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -38,10 +38,11 @@
 import com.android.keyguard.KeyguardVisibilityHelper;
 import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
 import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -160,6 +161,7 @@
             KeyguardStateController keyguardStateController,
             SysuiStatusBarStateController statusBarStateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
+            FeatureFlags featureFlags,
             DozeParameters dozeParameters,
             ScreenOffAnimationController screenOffAnimationController) {
         super(keyguardUserSwitcherView);
@@ -174,7 +176,8 @@
                 mUserSwitcherController, this);
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
                 keyguardStateController, dozeParameters,
-                screenOffAnimationController, /* animateYPos= */ false, /* logBuffer= */ null);
+                screenOffAnimationController, /* animateYPos= */ false,
+                featureFlags, /* logBuffer= */ null);
         mBackground = new KeyguardUserSwitcherScrim(context);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 53fed3d..7c96029 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -305,7 +305,8 @@
                             && editTextRootWindowInsets.isVisible(WindowInsets.Type.ime());
                     if (!mEntry.mRemoteEditImeVisible && !mEditText.mShowImeOnInputConnection) {
                         // Pass null to ensure all inputs are cleared for this entry b/227115380
-                        mController.removeRemoteInput(mEntry, null);
+                            mController.removeRemoteInput(mEntry, null,
+                                    /* reason= */"RemoteInputView$WindowInsetAnimation#onEnd");
                     }
                 }
             }
@@ -426,7 +427,7 @@
 
     @VisibleForTesting
     void onDefocus(boolean animate, boolean logClose, @Nullable Runnable doAfterDefocus) {
-        mController.removeRemoteInput(mEntry, mToken);
+        mController.removeRemoteInput(mEntry, mToken, /* reason= */"RemoteInputView#onDefocus");
         mEntry.remoteInputText = mEditText.getText();
 
         // During removal, we get reattached and lose focus. Not hiding in that
@@ -536,7 +537,8 @@
         if (mEntry.getRow().isChangingPosition() || isTemporarilyDetached()) {
             return;
         }
-        mController.removeRemoteInput(mEntry, mToken);
+        mController.removeRemoteInput(mEntry, mToken,
+                /* reason= */"RemoteInputView#onDetachedFromWindow");
         mController.removeSpinning(mEntry.getKey(), mToken);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
index a50fd6f..6c0d433 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -255,7 +255,8 @@
         entry.lastRemoteInputSent = SystemClock.elapsedRealtime()
         entry.mRemoteEditImeAnimatingAway = true
         remoteInputController.addSpinning(entry.key, view.mToken)
-        remoteInputController.removeRemoteInput(entry, view.mToken)
+        remoteInputController.removeRemoteInput(entry, view.mToken,
+               /* reason= */ "RemoteInputViewController#sendRemoteInput")
         remoteInputController.remoteInputSent(entry)
         entry.setHasSentReply()
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt
index 80f3d76..96717c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt
@@ -38,7 +38,8 @@
     /**
      * Fetches the connection statuses for the given [currentDevices] and invokes [callback] once
      * those statuses have been fetched. The fetching occurs on a background thread because IPCs may
-     * be required to fetch the statuses (see b/271058380).
+     * be required to fetch the statuses (see b/271058380). However, the callback will be invoked in
+     * the main thread.
      */
     fun fetchConnectionStatusInBackground(
         currentDevices: Collection<CachedBluetoothDevice>,
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index f404549..5fc435a 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -23,7 +23,6 @@
 import android.os.UserManager
 import android.provider.Settings
 import androidx.annotation.VisibleForTesting
-import com.android.systemui.res.R
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -31,6 +30,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.user.data.model.SelectedUserModel
 import com.android.systemui.user.data.model.SelectionStatus
@@ -132,7 +132,6 @@
                         Settings.Global.ADD_USERS_WHEN_LOCKED,
                         Settings.Global.USER_SWITCHER_ENABLED,
                     ),
-                userId = UserHandle.USER_SYSTEM,
             )
             .onStart { emit(Unit) } // Forces an initial update.
             .map { getSettings() }
@@ -247,7 +246,7 @@
     private suspend fun getSettings(): UserSwitcherSettingsModel {
         return withContext(backgroundDispatcher) {
             val isSimpleUserSwitcher =
-                globalSettings.getIntForUser(
+                globalSettings.getInt(
                     SETTING_SIMPLE_USER_SWITCHER,
                     if (
                         appContext.resources.getBoolean(
@@ -258,18 +257,16 @@
                     } else {
                         0
                     },
-                    UserHandle.USER_SYSTEM,
                 ) != 0
 
             val isAddUsersFromLockscreen =
-                globalSettings.getIntForUser(
+                globalSettings.getInt(
                     Settings.Global.ADD_USERS_WHEN_LOCKED,
                     0,
-                    UserHandle.USER_SYSTEM,
                 ) != 0
 
             val isUserSwitcherEnabled =
-                globalSettings.getIntForUser(
+                globalSettings.getInt(
                     Settings.Global.USER_SWITCHER_ENABLED,
                     if (
                         appContext.resources.getBoolean(
@@ -280,7 +277,6 @@
                     } else {
                         0
                     },
-                    UserHandle.USER_SYSTEM,
                 ) != 0
 
             UserSwitcherSettingsModel(
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index 760fe6a..f5edb7b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -42,6 +42,7 @@
      *   list, then list.get(i) could throw an IndexOutOfBoundsException. This method should not be
      *   used; try using `synchronized` or making a copy of the list instead.
      */
+    @Deprecated
     public static <T> void safeForeach(List<T> list, Consumer<T> c) {
         for (int i = list.size() - 1; i >= 0; i--) {
             T item = list.get(i);
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/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
index 47d505e..83ff789 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
@@ -18,19 +18,24 @@
 
 import com.android.systemui.util.time.SystemClock
 import com.android.systemui.util.time.SystemClockImpl
-import kotlinx.coroutines.CoroutineStart
 import java.util.concurrent.atomic.AtomicReference
+import kotlin.math.max
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.channelFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
-import kotlin.math.max
 
 /**
  * Returns a new [Flow] that combines the two most recent emissions from [this] using [transform].
@@ -44,8 +49,7 @@
     var previousValue: Any? = noVal
     collect { newVal ->
         if (previousValue != noVal) {
-            @Suppress("UNCHECKED_CAST")
-            emit(transform(previousValue as T, newVal))
+            @Suppress("UNCHECKED_CAST") emit(transform(previousValue as T, newVal))
         }
         previousValue = newVal
     }
@@ -60,13 +64,11 @@
 fun <T, R> Flow<T>.pairwiseBy(
     initialValue: T,
     transform: suspend (previousValue: T, newValue: T) -> R,
-): Flow<R> =
-    onStart { emit(initialValue) }.pairwiseBy(transform)
+): Flow<R> = onStart { emit(initialValue) }.pairwiseBy(transform)
 
 /**
  * Returns a new [Flow] that combines the two most recent emissions from [this] using [transform].
  *
- *
  * The output of [getInitialValue] will be used as the "old" value for the first emission. As
  * opposed to the initial value in the above [pairwiseBy], [getInitialValue] can do some work before
  * returning the initial value.
@@ -76,8 +78,7 @@
 fun <T, R> Flow<T>.pairwiseBy(
     getInitialValue: suspend () -> T,
     transform: suspend (previousValue: T, newValue: T) -> R,
-): Flow<R> =
-    onStart { emit(getInitialValue()) }.pairwiseBy(transform)
+): Flow<R> = onStart { emit(getInitialValue()) }.pairwiseBy(transform)
 
 /**
  * Returns a new [Flow] that produces the two most recent emissions from [this]. Note that the new
@@ -88,8 +89,8 @@
 fun <T> Flow<T>.pairwise(): Flow<WithPrev<T>> = pairwiseBy(::WithPrev)
 
 /**
- * Returns a new [Flow] that produces the two most recent emissions from [this]. [initialValue]
- * will be used as the "old" value for the first emission.
+ * Returns a new [Flow] that produces the two most recent emissions from [this]. [initialValue] will
+ * be used as the "old" value for the first emission.
  *
  * Useful for code that needs to compare the current value to the previous value.
  */
@@ -102,9 +103,9 @@
  * Returns a new [Flow] that combines the [Set] changes between each emission from [this] using
  * [transform].
  *
- * If [emitFirstEvent] is `true`, then the first [Set] emitted from the upstream [Flow] will cause
- * a change event to be emitted that contains no removals, and all elements from that first [Set]
- * as additions.
+ * If [emitFirstEvent] is `true`, then the first [Set] emitted from the upstream [Flow] will cause a
+ * change event to be emitted that contains no removals, and all elements from that first [Set] as
+ * additions.
  *
  * If [emitFirstEvent] is `false`, then the first emission is ignored and no changes are emitted
  * until a second [Set] has been emitted from the upstream [Flow].
@@ -112,22 +113,23 @@
 fun <T, R> Flow<Set<T>>.setChangesBy(
     transform: suspend (removed: Set<T>, added: Set<T>) -> R,
     emitFirstEvent: Boolean = true,
-): Flow<R> = (if (emitFirstEvent) onStart { emit(emptySet()) } else this)
-    .distinctUntilChanged()
-    .pairwiseBy { old: Set<T>, new: Set<T> ->
-        // If an element was present in the old set, but not the new one, then it was removed
-        val removed = old - new
-        // If an element is present in the new set, but on the old one, then it was added
-        val added = new - old
-        transform(removed, added)
-    }
+): Flow<R> =
+    (if (emitFirstEvent) onStart { emit(emptySet()) } else this)
+        .distinctUntilChanged()
+        .pairwiseBy { old: Set<T>, new: Set<T> ->
+            // If an element was present in the old set, but not the new one, then it was removed
+            val removed = old - new
+            // If an element is present in the new set, but on the old one, then it was added
+            val added = new - old
+            transform(removed, added)
+        }
 
 /**
  * Returns a new [Flow] that produces the [Set] changes between each emission from [this].
  *
- * If [emitFirstEvent] is `true`, then the first [Set] emitted from the upstream [Flow] will cause
- * a change event to be emitted that contains no removals, and all elements from that first [Set]
- * as additions.
+ * If [emitFirstEvent] is `true`, then the first [Set] emitted from the upstream [Flow] will cause a
+ * change event to be emitted that contains no removals, and all elements from that first [Set] as
+ * additions.
  *
  * If [emitFirstEvent] is `false`, then the first emission is ignored and no changes are emitted
  * until a second [Set] has been emitted from the upstream [Flow].
@@ -153,14 +155,11 @@
     coroutineScope {
         val noVal = Any()
         val sampledRef = AtomicReference(noVal)
-        val job = launch(Dispatchers.Unconfined) {
-            other.collect { sampledRef.set(it) }
-        }
+        val job = launch(Dispatchers.Unconfined) { other.collect { sampledRef.set(it) } }
         collect {
             val sampled = sampledRef.get()
             if (sampled != noVal) {
-                @Suppress("UNCHECKED_CAST")
-                emit(transform(it, sampled as B))
+                @Suppress("UNCHECKED_CAST") emit(transform(it, sampled as B))
             }
         }
         job.cancel()
@@ -181,7 +180,6 @@
  * latest value is emitted.
  *
  * Example:
- *
  * ```kotlin
  * flow {
  *     emit(1)     // t=0ms
@@ -210,7 +208,6 @@
  * during this period, only The latest value is emitted.
  *
  * Example:
- *
  * ```kotlin
  * flow {
  *     emit(1)     // t=0ms
@@ -248,10 +245,11 @@
                 // We create delayJob to allow cancellation during the delay period
                 delayJob = launch {
                     delay(timeUntilNextEmit)
-                    sendJob = outerScope.launch(start = CoroutineStart.UNDISPATCHED) {
-                        send(it)
-                        previousEmitTimeMs = clock.elapsedRealtime()
-                    }
+                    sendJob =
+                        outerScope.launch(start = CoroutineStart.UNDISPATCHED) {
+                            send(it)
+                            previousEmitTimeMs = clock.elapsedRealtime()
+                        }
                 }
             } else {
                 send(it)
@@ -259,4 +257,15 @@
             }
         }
     }
-}
\ No newline at end of file
+}
+
+/**
+ * Returns a [StateFlow] launched in the surrounding [CoroutineScope]. This [StateFlow] gets its
+ * value by invoking [getValue] whenever an event is emitted from [changedSignals]. It will also
+ * immediately invoke [getValue] to establish its initial value.
+ */
+inline fun <T> CoroutineScope.stateFlow(
+    changedSignals: Flow<Unit>,
+    crossinline getValue: () -> T,
+): StateFlow<T> =
+    changedSignals.map { getValue() }.stateIn(this, SharingStarted.Eagerly, getValue())
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
index 73e2f97..ffbc10a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
@@ -19,6 +19,7 @@
 class Utils {
     companion object {
         fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second)
+        fun <A, B, C> toTriple(ab: Pair<A, B>, c: C) = Triple(ab.first, ab.second, c)
 
         fun <A, B, C, D> toQuad(a: A, b: B, c: C, d: D) = Quad(a, b, c, d)
         fun <A, B, C, D> toQuad(a: A, bcd: Triple<B, C, D>) =
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/src/com/android/systemui/util/settings/GlobalSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
index 85fada2..42389f0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
@@ -16,22 +16,23 @@
 
 package com.android.systemui.util.settings;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.content.ContentResolver;
 import android.net.Uri;
 import android.provider.Settings;
 
-import com.android.systemui.settings.UserTracker;
-
 import javax.inject.Inject;
 
+// use UserHandle.USER_SYSTEM everywhere
+@SuppressLint("StaticSettingsProvider")
 class GlobalSettingsImpl implements GlobalSettings {
     private final ContentResolver mContentResolver;
-    private final UserTracker mUserTracker;
 
     @Inject
-    GlobalSettingsImpl(ContentResolver contentResolver, UserTracker userTracker) {
+    GlobalSettingsImpl(ContentResolver contentResolver) {
         mContentResolver = contentResolver;
-        mUserTracker = userTracker;
     }
 
     @Override
@@ -40,43 +41,23 @@
     }
 
     @Override
-    public UserTracker getUserTracker() {
-        return mUserTracker;
-    }
-
-    @Override
     public Uri getUriFor(String name) {
         return Settings.Global.getUriFor(name);
     }
 
     @Override
-    public String getStringForUser(String name, int userHandle) {
-        return Settings.Global.getStringForUser(mContentResolver, name,
-                getRealUserHandle(userHandle));
+    public String getString(String name) {
+        return Settings.Global.getString(mContentResolver, name);
     }
 
     @Override
-    public boolean putString(String name, String value, boolean overrideableByRestore) {
-        throw new UnsupportedOperationException(
-                "This method only exists publicly for Settings.System and Settings.Secure");
+    public boolean putString(String name, String value) {
+        return Settings.Global.putString(mContentResolver, name, value);
     }
 
     @Override
-    public boolean putStringForUser(String name, String value, int userHandle) {
-        return Settings.Global.putStringForUser(mContentResolver, name, value,
-                getRealUserHandle(userHandle));
-    }
-
-    @Override
-    public boolean putStringForUser(String name, String value, String tag, boolean makeDefault,
-            int userHandle, boolean overrideableByRestore) {
-        return Settings.Global.putStringForUser(
-                mContentResolver, name, value, tag, makeDefault, getRealUserHandle(userHandle),
-                overrideableByRestore);
-    }
-
-    @Override
-    public boolean putString(String name, String value, String tag, boolean makeDefault) {
+    public boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag,
+            boolean makeDefault) {
         return Settings.Global.putString(mContentResolver, name, value, tag, makeDefault);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java
index 798033e..6031a4e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java
@@ -22,5 +22,5 @@
  * See {@link SettingsProxy} for details.
  */
 
-public interface SecureSettings extends SettingsProxy {
+public interface SecureSettings extends UserSettingsProxy {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
index f995436..6532ce8 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
@@ -20,6 +20,8 @@
 import android.net.Uri;
 import android.provider.Settings;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.settings.UserTracker;
 
 import javax.inject.Inject;
@@ -75,7 +77,7 @@
     }
 
     @Override
-    public boolean putString(String name, String value, String tag, boolean makeDefault) {
+    public boolean putString(@NonNull String name, String value, String tag, boolean makeDefault) {
         return Settings.Secure.putString(mContentResolver, name, value, tag, makeDefault);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
index b6846a3..6a9edc1 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
@@ -18,25 +18,22 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UserIdInt;
 import android.content.ContentResolver;
 import android.database.ContentObserver;
 import android.net.Uri;
-import android.os.UserHandle;
 import android.provider.Settings;
 
-import com.android.systemui.settings.UserTracker;
-
 /**
- * Used to interact with Settings.Secure, Settings.Global, and Settings.System.
- *
+ * Used to interact with mainly with Settings.Global, but can also be used for Settings.System
+ * and Settings.Secure. To use the per-user System and Secure settings, {@link UserSettingsProxy}
+ * must be used instead.
+ * <p>
  * This interface can be implemented to give instance method (instead of static method) versions
- * of Settings.Secure, Settings.Global, and Settings.System. It can be injected into class
- * constructors and then faked or mocked as needed in tests.
- *
- * You can ask for {@link SecureSettings}, {@link GlobalSettings}, or {@link SystemSettings} to be
- * injected as needed.
- *
+ * of Settings.Global. It can be injected into class constructors and then faked or mocked as needed
+ * in tests.
+ * <p>
+ * You can ask for {@link GlobalSettings} to be injected as needed.
+ * <p>
  * This class also provides {@link #registerContentObserver(String, ContentObserver)} methods,
  * normally found on {@link ContentResolver} instances, unifying setting related actions in one
  * place.
@@ -49,29 +46,6 @@
     ContentResolver getContentResolver();
 
     /**
-     * Returns that {@link UserTracker} this instance was constructed with.
-     */
-    UserTracker getUserTracker();
-
-    /**
-     * Returns the user id for the associated {@link ContentResolver}.
-     */
-    default int getUserId() {
-        return getContentResolver().getUserId();
-    }
-
-    /**
-     * Returns the actual current user handle when querying with the current user. Otherwise,
-     * returns the passed in user id.
-     */
-    default int getRealUserHandle(int userHandle) {
-        if (userHandle != UserHandle.USER_CURRENT) {
-            return userHandle;
-        }
-        return getUserTracker().getUserId();
-    }
-
-    /**
      * Construct the content URI for a particular name/value pair,
      * useful for monitoring changes with a ContentObserver.
      * @param name to look up in the table
@@ -82,7 +56,7 @@
     /**
      * Convenience wrapper around
      * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
-     *
+     * <p>
      * Implicitly calls {@link #getUriFor(String)} on the passed in name.
      */
     default void registerContentObserver(String name, ContentObserver settingsObserver) {
@@ -94,13 +68,13 @@
      * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
      */
     default void registerContentObserver(Uri uri, ContentObserver settingsObserver) {
-        registerContentObserverForUser(uri, settingsObserver, getUserId());
+        registerContentObserver(uri, false, settingsObserver);
     }
 
     /**
      * Convenience wrapper around
      * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
-     *
+     * <p>
      * Implicitly calls {@link #getUriFor(String)} on the passed in name.
      */
     default void registerContentObserver(String name, boolean notifyForDescendants,
@@ -114,53 +88,8 @@
      */
     default void registerContentObserver(Uri uri, boolean notifyForDescendants,
             ContentObserver settingsObserver) {
-        registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId());
-    }
-
-    /**
-     * Convenience wrapper around
-     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
-     *
-     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
-     */
-    default void registerContentObserverForUser(
-            String name, ContentObserver settingsObserver, int userHandle) {
-        registerContentObserverForUser(
-                getUriFor(name), settingsObserver, userHandle);
-    }
-
-    /**
-     * Convenience wrapper around
-     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
-     */
-    default void registerContentObserverForUser(
-            Uri uri, ContentObserver settingsObserver, int userHandle) {
-        registerContentObserverForUser(
-                uri, false, settingsObserver, userHandle);
-    }
-
-    /**
-     * Convenience wrapper around
-     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
-     *
-     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
-     */
-    default void registerContentObserverForUser(
-            String name, boolean notifyForDescendants, ContentObserver settingsObserver,
-            int userHandle) {
-        registerContentObserverForUser(
-                getUriFor(name), notifyForDescendants, settingsObserver, userHandle);
-    }
-
-    /**
-     * Convenience wrapper around
-     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
-     */
-    default void registerContentObserverForUser(
-            Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver,
-            int userHandle) {
         getContentResolver().registerContentObserver(
-                uri, notifyForDescendants, settingsObserver, getRealUserHandle(userHandle));
+                uri, notifyForDescendants, settingsObserver);
     }
 
     /** See {@link ContentResolver#unregisterContentObserver(ContentObserver)}. */
@@ -173,22 +102,7 @@
      * @param name to look up in the table
      * @return the corresponding value, or null if not present
      */
-    default String getString(String name) {
-        return getStringForUser(name, getUserId());
-    }
-
-    /**See {@link #getString(String)}. */
-    String getStringForUser(String name, int userHandle);
-
-    /**
-     * Store a name/value pair into the database. Values written by this method will be
-     * overridden if a restore happens in the future.
-     *
-     * @param name to store
-     * @param value to associate with the name
-     * @return true if the value was set, false on database errors
-     */
-    boolean putString(String name, String value, boolean overrideableByRestore);
+    String getString(String name);
 
     /**
      * Store a name/value pair into the database.
@@ -196,16 +110,7 @@
      * @param value to associate with the name
      * @return true if the value was set, false on database errors
      */
-    default boolean putString(String name, String value) {
-        return putStringForUser(name, value, getUserId());
-    }
-
-    /** See {@link #putString(String, String)}. */
-    boolean putStringForUser(String name, String value, int userHandle);
-
-    /** See {@link #putString(String, String)}. */
-    boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag,
-            boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore);
+    boolean putString(String name, String value);
 
     /**
      * Store a name/value pair into the database.
@@ -262,12 +167,7 @@
      * or not a valid integer.
      */
     default int getInt(String name, int def) {
-        return getIntForUser(name, def, getUserId());
-    }
-
-    /** See {@link #getInt(String, int)}. */
-    default int getIntForUser(String name, int def, int userHandle) {
-        String v = getStringForUser(name, userHandle);
+        String v = getString(name);
         try {
             return v != null ? Integer.parseInt(v) : def;
         } catch (NumberFormatException e) {
@@ -292,14 +192,9 @@
      *
      * @return The setting's current value.
      */
-    default int getInt(String name) throws Settings.SettingNotFoundException {
-        return getIntForUser(name, getUserId());
-    }
-
-    /** See {@link #getInt(String)}. */
-    default int getIntForUser(String name, int userHandle)
+    default int getInt(String name)
             throws Settings.SettingNotFoundException {
-        String v = getStringForUser(name, userHandle);
+        String v = getString(name);
         try {
             return Integer.parseInt(v);
         } catch (NumberFormatException e) {
@@ -320,12 +215,7 @@
      * @return true if the value was set, false on database errors
      */
     default boolean putInt(String name, int value) {
-        return putIntForUser(name, value, getUserId());
-    }
-
-    /** See {@link #putInt(String, int)}. */
-    default boolean putIntForUser(String name, int value, int userHandle) {
-        return putStringForUser(name, Integer.toString(value), userHandle);
+        return putString(name, Integer.toString(value));
     }
 
     /**
@@ -342,12 +232,7 @@
      * or not a valid boolean.
      */
     default boolean getBool(String name, boolean def) {
-        return getBoolForUser(name, def, getUserId());
-    }
-
-    /** See {@link #getBool(String, boolean)}. */
-    default boolean getBoolForUser(String name, boolean def, int userHandle) {
-        return getIntForUser(name, def ? 1 : 0, userHandle) != 0;
+        return getInt(name, def ? 1 : 0) != 0;
     }
 
     /**
@@ -367,14 +252,9 @@
      *
      * @return The setting's current value.
      */
-    default boolean getBool(String name) throws Settings.SettingNotFoundException {
-        return getBoolForUser(name, getUserId());
-    }
-
-    /** See {@link #getBool(String)}. */
-    default boolean getBoolForUser(String name, int userHandle)
+    default boolean getBool(String name)
             throws Settings.SettingNotFoundException {
-        return getIntForUser(name, userHandle) != 0;
+        return getInt(name) != 0;
     }
 
     /**
@@ -390,12 +270,7 @@
      * @return true if the value was set, false on database errors
      */
     default boolean putBool(String name, boolean value) {
-        return putBoolForUser(name, value, getUserId());
-    }
-
-    /** See {@link #putBool(String, boolean)}. */
-    default boolean putBoolForUser(String name, boolean value, int userHandle) {
-        return putIntForUser(name, value ? 1 : 0, userHandle);
+        return putInt(name, value ? 1 : 0);
     }
 
     /**
@@ -412,12 +287,12 @@
      * or not a valid {@code long}.
      */
     default long getLong(String name, long def) {
-        return getLongForUser(name, def, getUserId());
+        String valString = getString(name);
+        return parseLongOrUseDefault(valString, def);
     }
 
-    /** See {@link #getLong(String, long)}. */
-    default long getLongForUser(String name, long def, int userHandle) {
-        String valString = getStringForUser(name, userHandle);
+    /** Convert a string to a long, or uses a default if the string is malformed or null */
+    static long parseLongOrUseDefault(String valString, long def) {
         long value;
         try {
             value = valString != null ? Long.parseLong(valString) : def;
@@ -443,14 +318,15 @@
      * @throws Settings.SettingNotFoundException Thrown if a setting by the given
      * name can't be found or the setting value is not an integer.
      */
-    default long getLong(String name) throws Settings.SettingNotFoundException {
-        return getLongForUser(name, getUserId());
+    default long getLong(String name)
+            throws Settings.SettingNotFoundException {
+        String valString = getString(name);
+        return parseLongOrThrow(name, valString);
     }
 
-    /** See {@link #getLong(String)}. */
-    default long getLongForUser(String name, int userHandle)
+    /** Convert a string to a long, or throws an exception if the string is malformed or null */
+    static long parseLongOrThrow(String name, String valString)
             throws Settings.SettingNotFoundException {
-        String valString = getStringForUser(name, userHandle);
         try {
             return Long.parseLong(valString);
         } catch (NumberFormatException e) {
@@ -471,12 +347,7 @@
      * @return true if the value was set, false on database errors
      */
     default boolean putLong(String name, long value) {
-        return putLongForUser(name, value, getUserId());
-    }
-
-    /** See {@link #putLong(String, long)}. */
-    default boolean putLongForUser(String name, long value, int userHandle) {
-        return putStringForUser(name, Long.toString(value), userHandle);
+        return putString(name, Long.toString(value));
     }
 
     /**
@@ -493,12 +364,12 @@
      * or not a valid float.
      */
     default float getFloat(String name, float def) {
-        return getFloatForUser(name, def, getUserId());
+        String v = getString(name);
+        return parseFloat(v, def);
     }
 
-    /** See {@link #getFloat(String)}. */
-    default float getFloatForUser(String name, float def, int userHandle) {
-        String v = getStringForUser(name, userHandle);
+    /** Convert a string to a float, or uses a default if the string is malformed or null */
+    static float parseFloat(String v, float def) {
         try {
             return v != null ? Float.parseFloat(v) : def;
         } catch (NumberFormatException e) {
@@ -523,14 +394,15 @@
      *
      * @return The setting's current value.
      */
-    default float getFloat(String name) throws Settings.SettingNotFoundException {
-        return getFloatForUser(name, getUserId());
+    default float getFloat(String name)
+            throws Settings.SettingNotFoundException {
+        String v = getString(name);
+        return parseFloatOrThrow(name, v);
     }
 
-    /** See {@link #getFloat(String, float)}. */
-    default float getFloatForUser(String name, int userHandle)
+    /** Convert a string to a float, or throws an exception if the string is malformed or null */
+    static float parseFloatOrThrow(String name, String v)
             throws Settings.SettingNotFoundException {
-        String v = getStringForUser(name, userHandle);
         if (v == null) {
             throw new Settings.SettingNotFoundException(name);
         }
@@ -554,11 +426,6 @@
      * @return true if the value was set, false on database errors
      */
     default boolean putFloat(String name, float value) {
-        return putFloatForUser(name, value, getUserId());
-    }
-
-    /** See {@link #putFloat(String, float)} */
-    default boolean putFloatForUser(String name, float value, int userHandle) {
-        return putStringForUser(name, Float.toString(value), userHandle);
+        return putString(name, Float.toString(value));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
index 561495e..7484368 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
@@ -27,7 +27,7 @@
 object SettingsProxyExt {
 
     /** Returns a flow of [Unit] that is invoked each time that content is updated. */
-    fun SettingsProxy.observerFlow(
+    fun UserSettingsProxy.observerFlow(
         @UserIdInt userId: Int,
         vararg names: String,
     ): Flow<Unit> {
@@ -44,4 +44,22 @@
             awaitClose { unregisterContentObserver(observer) }
         }
     }
+
+    /** Returns a flow of [Unit] that is invoked each time that content is updated. */
+    fun SettingsProxy.observerFlow(
+        vararg names: String,
+    ): Flow<Unit> {
+        return conflatedCallbackFlow {
+            val observer =
+                object : ContentObserver(null) {
+                    override fun onChange(selfChange: Boolean) {
+                        trySend(Unit)
+                    }
+                }
+
+            names.forEach { name -> registerContentObserver(name, observer) }
+
+            awaitClose { unregisterContentObserver(observer) }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java
index d57d749..c67c603 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java
@@ -21,5 +21,5 @@
  *
  * See {@link SettingsProxy} for details.
  */
-public interface SystemSettings extends SettingsProxy {
+public interface SystemSettings extends UserSettingsProxy {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
index fba7ddf..658b299 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
@@ -20,6 +20,8 @@
 import android.net.Uri;
 import android.provider.Settings;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.settings.UserTracker;
 
 import javax.inject.Inject;
@@ -74,7 +76,7 @@
     }
 
     @Override
-    public boolean putString(String name, String value, String tag, boolean makeDefault) {
+    public boolean putString(@NonNull String name, String value, String tag, boolean makeDefault) {
         throw new UnsupportedOperationException(
                 "This method only exists publicly for Settings.Secure and Settings.Global");
     }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
new file mode 100644
index 0000000..0d6c0f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
@@ -0,0 +1,272 @@
+/*
+ * 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.settings;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.systemui.settings.UserTracker;
+
+/**
+ * Used to interact with per-user Settings.Secure and Settings.System settings (but not
+ * Settings.Global, since those do not vary per-user)
+ * <p>
+ * This interface can be implemented to give instance method (instead of static method) versions
+ * of Settings.Secure and Settings.System. It can be injected into class constructors and then
+ * faked or mocked as needed in tests.
+ * <p>
+ * You can ask for {@link SecureSettings} or {@link SystemSettings} to be injected as needed.
+ * <p>
+ * This class also provides {@link #registerContentObserver(String, ContentObserver)} methods,
+ * normally found on {@link ContentResolver} instances, unifying setting related actions in one
+ * place.
+ */
+public interface UserSettingsProxy extends SettingsProxy {
+
+    /**
+     * Returns that {@link UserTracker} this instance was constructed with.
+     */
+    UserTracker getUserTracker();
+
+    /**
+     * Returns the user id for the associated {@link ContentResolver}.
+     */
+    default int getUserId() {
+        return getContentResolver().getUserId();
+    }
+
+    /**
+     * Returns the actual current user handle when querying with the current user. Otherwise,
+     * returns the passed in user id.
+     */
+    default int getRealUserHandle(int userHandle) {
+        if (userHandle != UserHandle.USER_CURRENT) {
+            return userHandle;
+        }
+        return getUserTracker().getUserId();
+    }
+
+    @Override
+    default void registerContentObserver(Uri uri, ContentObserver settingsObserver) {
+        registerContentObserverForUser(uri, settingsObserver, getUserId());
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+     */
+    @Override
+    default void registerContentObserver(Uri uri, boolean notifyForDescendants,
+            ContentObserver settingsObserver) {
+        registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId());
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     *
+     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+     */
+    default void registerContentObserverForUser(
+            String name, ContentObserver settingsObserver, int userHandle) {
+        registerContentObserverForUser(
+                getUriFor(name), settingsObserver, userHandle);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     */
+    default void registerContentObserverForUser(
+            Uri uri, ContentObserver settingsObserver, int userHandle) {
+        registerContentObserverForUser(
+                uri, false, settingsObserver, userHandle);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     *
+     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+     */
+    default void registerContentObserverForUser(
+            String name, boolean notifyForDescendants, ContentObserver settingsObserver,
+            int userHandle) {
+        registerContentObserverForUser(
+                getUriFor(name), notifyForDescendants, settingsObserver, userHandle);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     */
+    default void registerContentObserverForUser(
+            Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver,
+            int userHandle) {
+        getContentResolver().registerContentObserver(
+                uri, notifyForDescendants, settingsObserver, getRealUserHandle(userHandle));
+    }
+
+    /**
+     * Look up a name in the database.
+     * @param name to look up in the table
+     * @return the corresponding value, or null if not present
+     */
+    @Override
+    default String getString(String name) {
+        return getStringForUser(name, getUserId());
+    }
+
+    /**See {@link #getString(String)}. */
+    String getStringForUser(String name, int userHandle);
+
+    /**
+     * Store a name/value pair into the database. Values written by this method will be
+     * overridden if a restore happens in the future.
+     *
+     * @param name to store
+     * @param value to associate with the name
+     * @return true if the value was set, false on database errors
+     */
+    boolean putString(String name, String value, boolean overrideableByRestore);
+
+    @Override
+    default boolean putString(String name, String value) {
+        return putStringForUser(name, value, getUserId());
+    }
+
+    /** See {@link #putString(String, String)}. */
+    boolean putStringForUser(String name, String value, int userHandle);
+
+    /** See {@link #putString(String, String)}. */
+    boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag,
+            boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore);
+
+    @Override
+    default int getInt(String name, int def) {
+        return getIntForUser(name, def, getUserId());
+    }
+
+    /** See {@link #getInt(String, int)}. */
+    default int getIntForUser(String name, int def, int userHandle) {
+        String v = getStringForUser(name, userHandle);
+        try {
+            return v != null ? Integer.parseInt(v) : def;
+        } catch (NumberFormatException e) {
+            return def;
+        }
+    }
+
+    @Override
+    default int getInt(String name) throws Settings.SettingNotFoundException {
+        return getIntForUser(name, getUserId());
+    }
+
+    /** See {@link #getInt(String)}. */
+    default int getIntForUser(String name, int userHandle)
+            throws Settings.SettingNotFoundException {
+        String v = getStringForUser(name, userHandle);
+        try {
+            return Integer.parseInt(v);
+        } catch (NumberFormatException e) {
+            throw new Settings.SettingNotFoundException(name);
+        }
+    }
+
+    @Override
+    default boolean putInt(String name, int value) {
+        return putIntForUser(name, value, getUserId());
+    }
+
+    /** See {@link #putInt(String, int)}. */
+    default boolean putIntForUser(String name, int value, int userHandle) {
+        return putStringForUser(name, Integer.toString(value), userHandle);
+    }
+
+    @Override
+    default boolean getBool(String name, boolean def) {
+        return getBoolForUser(name, def, getUserId());
+    }
+
+    /** See {@link #getBool(String, boolean)}. */
+    default boolean getBoolForUser(String name, boolean def, int userHandle) {
+        return getIntForUser(name, def ? 1 : 0, userHandle) != 0;
+    }
+
+    @Override
+    default boolean getBool(String name) throws Settings.SettingNotFoundException {
+        return getBoolForUser(name, getUserId());
+    }
+
+    /** See {@link #getBool(String)}. */
+    default boolean getBoolForUser(String name, int userHandle)
+            throws Settings.SettingNotFoundException {
+        return getIntForUser(name, userHandle) != 0;
+    }
+
+    @Override
+    default boolean putBool(String name, boolean value) {
+        return putBoolForUser(name, value, getUserId());
+    }
+
+    /** See {@link #putBool(String, boolean)}. */
+    default boolean putBoolForUser(String name, boolean value, int userHandle) {
+        return putIntForUser(name, value ? 1 : 0, userHandle);
+    }
+
+    /** See {@link #getLong(String, long)}. */
+    default long getLongForUser(String name, long def, int userHandle) {
+        String valString = getStringForUser(name, userHandle);
+        return SettingsProxy.parseLongOrUseDefault(valString, def);
+    }
+
+    /** See {@link #getLong(String)}. */
+    default long getLongForUser(String name, int userHandle)
+            throws Settings.SettingNotFoundException {
+        String valString = getStringForUser(name, userHandle);
+        return SettingsProxy.parseLongOrThrow(name, valString);
+    }
+
+    /** See {@link #putLong(String, long)}. */
+    default boolean putLongForUser(String name, long value, int userHandle) {
+        return putStringForUser(name, Long.toString(value), userHandle);
+    }
+
+    /** See {@link #getFloat(String)}. */
+    default float getFloatForUser(String name, float def, int userHandle) {
+        String v = getStringForUser(name, userHandle);
+        return SettingsProxy.parseFloat(v, def);
+    }
+
+    /** See {@link #getFloat(String, float)}. */
+    default float getFloatForUser(String name, int userHandle)
+            throws Settings.SettingNotFoundException {
+        String v = getStringForUser(name, userHandle);
+        return SettingsProxy.parseFloatOrThrow(name, v);
+    }
+
+    /** See {@link #putFloat(String, float)} */
+    default boolean putFloatForUser(String name, float value, int userHandle) {
+        return putStringForUser(name, Float.toString(value), userHandle);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt b/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt
new file mode 100644
index 0000000..51d2afa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.util.ui
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.transformLatest
+
+/**
+ * A state comprised of a [value] of type [T] paired with a boolean indicating whether or not the
+ * [value] [isAnimating] in the UI.
+ */
+data class AnimatedValue<T>(
+    val value: T,
+    val isAnimating: Boolean,
+)
+
+/**
+ * An event comprised of a [value] of type [T] paired with a [boolean][startAnimating] indicating
+ * whether or not this event should start an animation.
+ */
+data class AnimatableEvent<T>(
+    val value: T,
+    val startAnimating: Boolean,
+)
+
+/**
+ * Returns a [Flow] that tracks an [AnimatedValue] state. The input [Flow] is used to update the
+ * [AnimatedValue.value], as well as [AnimatedValue.isAnimating] if the event's
+ * [AnimatableEvent.startAnimating] value is `true`. When [completionEvents] emits a value, the
+ * [AnimatedValue.isAnimating] will flip to `false`.
+ */
+fun <T> Flow<AnimatableEvent<T>>.toAnimatedValueFlow(
+    completionEvents: Flow<Any?>,
+): Flow<AnimatedValue<T>> = transformLatest { (value, startAnimating) ->
+    emit(AnimatedValue(value, isAnimating = startAnimating))
+    if (startAnimating) {
+        // Wait for a completion now that we've started animating
+        completionEvents
+            .map { Unit } // replace the event so that it's never `null`
+            .firstOrNull() // `null` indicates an empty flow
+            // emit the new state if the flow was not empty.
+            ?.run { emit(AnimatedValue(value, isAnimating = false)) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index ea4d31b..d65a69c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -77,6 +77,8 @@
 import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.concurrency.ThreadFactory;
 
+import dalvik.annotation.optimization.NeverCompile;
+
 import java.io.PrintWriter;
 import java.util.HashMap;
 import java.util.List;
@@ -286,6 +288,7 @@
         return new MediaSessions(context, looper, callbacks);
     }
 
+    @NeverCompile
     public void dump(PrintWriter pw, String[] args) {
         pw.println(VolumeDialogControllerImpl.class.getSimpleName() + " state:");
         pw.print("  mVolumePolicy: "); pw.println(mVolumePolicy);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 727d649..929b91c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -135,6 +135,9 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.AlphaTintDrawableWrapper;
 import com.android.systemui.util.RoundedCornerProgressDrawable;
+import com.android.systemui.util.settings.SecureSettings;
+
+import dagger.Lazy;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -304,6 +307,8 @@
     private @DevicePostureController.DevicePostureInt int mDevicePosture;
     private int mOrientation;
     private final FeatureFlags mFeatureFlags;
+    private final Lazy<SecureSettings> mSecureSettings;
+    private int mDialogTimeoutMillis;
 
     public VolumeDialogImpl(
             Context context,
@@ -320,7 +325,8 @@
             DevicePostureController devicePostureController,
             Looper looper,
             DumpManager dumpManager,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            Lazy<SecureSettings> secureSettings) {
         mFeatureFlags = featureFlags;
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
@@ -351,6 +357,8 @@
         mUseBackgroundBlur =
             mContext.getResources().getBoolean(R.bool.config_volumeDialogUseBackgroundBlur);
         mInteractionJankMonitor = interactionJankMonitor;
+        mSecureSettings = secureSettings;
+        mDialogTimeoutMillis = DIALOG_TIMEOUT_MILLIS;
 
         dumpManager.registerDumpable("VolumeDialogImpl", this);
 
@@ -515,6 +523,8 @@
         mDialog.setContentView(R.layout.volume_dialog);
         mDialogView = mDialog.findViewById(R.id.volume_dialog);
         mDialogView.setAlpha(0);
+        mDialogTimeoutMillis = mSecureSettings.get().getInt(
+                Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, DIALOG_TIMEOUT_MILLIS);
         mDialog.setCanceledOnTouchOutside(true);
         mDialog.setOnShowListener(dialog -> {
             mDialogView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
@@ -527,7 +537,7 @@
                     .alpha(1)
                     .translationX(0)
                     .setDuration(mDialogShowAnimationDurationMs)
-                    .setListener(getJankListener(getDialogView(), TYPE_SHOW, DIALOG_TIMEOUT_MILLIS))
+                    .setListener(getJankListener(getDialogView(), TYPE_SHOW, mDialogTimeoutMillis))
                     .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
                     .withEndAction(() -> {
                         if (!Prefs.getBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, false)) {
@@ -1514,7 +1524,7 @@
                     AccessibilityManager.FLAG_CONTENT_TEXT
                             | AccessibilityManager.FLAG_CONTENT_CONTROLS);
         }
-        return mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_TIMEOUT_MILLIS,
+        return mAccessibilityMgr.getRecommendedTimeoutMillis(mDialogTimeoutMillis,
                 AccessibilityManager.FLAG_CONTENT_CONTROLS);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index cc9f3e1..e3b3c21 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -31,6 +31,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.volume.CsdWarningDialog;
 import com.android.systemui.volume.VolumeComponent;
 import com.android.systemui.volume.VolumeDialogComponent;
@@ -38,6 +39,7 @@
 import com.android.systemui.volume.VolumePanelFactory;
 
 import dagger.Binds;
+import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
@@ -63,7 +65,8 @@
             CsdWarningDialog.Factory csdFactory,
             DevicePostureController devicePostureController,
             DumpManager dumpManager,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            Lazy<SecureSettings> secureSettings) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
                 context,
                 volumeDialogController,
@@ -79,7 +82,8 @@
                 devicePostureController,
                 Looper.getMainLooper(),
                 dumpManager,
-                featureFlags);
+                featureFlags,
+                secureSettings);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
         impl.setSilentMode(false);
diff --git a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
index c1be44a..167e341 100644
--- a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
+++ b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
@@ -81,7 +81,7 @@
     @get:Provides val notificationStackSizeCalculator: NotificationStackSizeCalculator = mock(),
     @get:Provides val notificationWakeUpCoordinator: NotificationWakeUpCoordinator = mock(),
     @get:Provides val screenLifecycle: ScreenLifecycle = mock(),
-    @get:Provides val screenOffAnimController: ScreenOffAnimationController = mock(),
+    @get:Provides val screenOffAnimationController: ScreenOffAnimationController = mock(),
     @get:Provides val scrimController: ScrimController = mock(),
     @get:Provides val statusBarStateController: SysuiStatusBarStateController = mock(),
     @get:Provides val statusBarWindowController: StatusBarWindowController = mock(),
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 6afa525..4d8768f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -25,9 +25,11 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.plugins.ClockAnimations
 import com.android.systemui.plugins.ClockController
@@ -47,8 +49,8 @@
 import java.util.TimeZone
 import java.util.concurrent.Executor
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.yield
 import org.junit.Assert.assertEquals
 import org.junit.Before
@@ -90,10 +92,10 @@
     @Mock private lateinit var smallClockEvents: ClockFaceEvents
     @Mock private lateinit var largeClockEvents: ClockFaceEvents
     @Mock private lateinit var parentView: View
-    @Mock private lateinit var transitionRepository: KeyguardTransitionRepository
     private lateinit var repository: FakeKeyguardRepository
     @Mock private lateinit var smallLogBuffer: LogBuffer
     @Mock private lateinit var largeLogBuffer: LogBuffer
+    @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     private lateinit var underTest: ClockEventController
 
     @Before
@@ -125,17 +127,13 @@
 
         withDeps.featureFlags.apply {
             set(Flags.REGION_SAMPLING, false)
-            set(Flags.DOZING_MIGRATION_1, false)
+            set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false)
             set(Flags.FACE_AUTH_REFACTOR, false)
         }
         underTest =
             ClockEventController(
                 withDeps.keyguardInteractor,
-                KeyguardTransitionInteractorFactory.create(
-                        scope = TestScope().backgroundScope,
-                        featureFlags = withDeps.featureFlags,
-                    )
-                    .keyguardTransitionInteractor,
+                keyguardTransitionInteractor,
                 broadcastDispatcher,
                 batteryController,
                 keyguardUpdateMonitor,
@@ -316,6 +314,68 @@
         }
 
     @Test
+    fun listenForDozeAmountTransition_updatesClockDozeAmount() =
+        runBlocking(IMMEDIATE) {
+            val transitionStep = MutableStateFlow(TransitionStep())
+            whenever(keyguardTransitionInteractor.dozeAmountTransition).thenReturn(transitionStep)
+
+            val job = underTest.listenForDozeAmountTransition(this)
+            transitionStep.value =
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 0.4f
+                )
+            yield()
+
+            verify(animations, times(2)).doze(0.4f)
+
+            job.cancel()
+        }
+
+    @Test
+    fun listenForTransitionToAodFromGone_updatesClockDozeAmountToOne() =
+        runBlocking(IMMEDIATE) {
+            val transitionStep = MutableStateFlow(TransitionStep())
+            whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD))
+                .thenReturn(transitionStep)
+
+            val job = underTest.listenForAnyStateToAodTransition(this)
+            transitionStep.value =
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    transitionState = TransitionState.STARTED,
+                )
+            yield()
+
+            verify(animations, times(2)).doze(1f)
+
+            job.cancel()
+        }
+
+    @Test
+    fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() =
+        runBlocking(IMMEDIATE) {
+            val transitionStep = MutableStateFlow(TransitionStep())
+            whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD))
+                .thenReturn(transitionStep)
+
+            val job = underTest.listenForAnyStateToAodTransition(this)
+            transitionStep.value =
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    transitionState = TransitionState.STARTED,
+                )
+            yield()
+
+            verify(animations, never()).doze(1f)
+
+            job.cancel()
+        }
+
+    @Test
     fun unregisterListeners_validate() =
         runBlocking(IMMEDIATE) {
             underTest.unregisterListeners()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 62f9a9d..20d4eb9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -810,7 +810,6 @@
                     SceneKey.Bouncer,
                     flowOf(.5f),
                     false,
-                    isUserInputOngoing = flowOf(false),
                 )
             runCurrent()
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
@@ -826,8 +825,7 @@
                     SceneKey.Bouncer,
                     SceneKey.Gone,
                     flowOf(.5f),
-                    false,
-                    isUserInputOngoing = flowOf(false),
+                    false
                 )
             runCurrent()
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
@@ -844,8 +842,7 @@
                     SceneKey.Gone,
                     SceneKey.Bouncer,
                     flowOf(.5f),
-                    false,
-                    isUserInputOngoing = flowOf(false),
+                    false
                 )
             runCurrent()
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
@@ -863,8 +860,7 @@
                     SceneKey.Bouncer,
                     SceneKey.Gone,
                     flowOf(.5f),
-                    false,
-                    isUserInputOngoing = flowOf(false),
+                    false
                 )
             runCurrent()
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
@@ -880,7 +876,6 @@
                     SceneKey.Lockscreen,
                     flowOf(.5f),
                     false,
-                    isUserInputOngoing = flowOf(false),
                 )
             runCurrent()
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason")
@@ -898,7 +893,6 @@
                     SceneKey.Gone,
                     flowOf(.5f),
                     false,
-                    isUserInputOngoing = flowOf(false),
                 )
             runCurrent()
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 989164e..22c75d8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -16,11 +16,12 @@
 
 package com.android.keyguard;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.animation.LayoutTransition;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
@@ -32,6 +33,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.power.data.repository.FakePowerRepository;
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
@@ -61,6 +63,7 @@
     @Mock protected FeatureFlags mFeatureFlags;
     @Mock protected InteractionJankMonitor mInteractionJankMonitor;
     @Mock protected ViewTreeObserver mViewTreeObserver;
+    @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     @Mock protected DumpManager mDumpManager;
     protected FakeKeyguardRepository mFakeKeyguardRepository;
     protected FakePowerRepository mFakePowerRepository;
@@ -69,7 +72,6 @@
 
     @Mock protected KeyguardClockSwitch mKeyguardClockSwitch;
     @Mock protected FrameLayout mMediaHostContainer;
-    @Mock protected LayoutTransition mMediaLayoutTransition;
 
     @Before
     public void setup() {
@@ -92,6 +94,7 @@
                 mFeatureFlags,
                 mInteractionJankMonitor,
                 deps.getKeyguardInteractor(),
+                mKeyguardTransitionInteractor,
                 mDumpManager,
                 PowerInteractorFactory.create(
                         mFakePowerRepository
@@ -107,8 +110,8 @@
                 };
 
         when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
-
         when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch);
+        when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow());
     }
 
     protected void givenViewAttached() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 9a908d7..948942f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -16,19 +16,24 @@
 
 package com.android.keyguard;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.animation.LayoutTransition;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
 
+import com.android.app.animation.Interpolators;
+import com.android.systemui.animation.ViewHierarchyAnimator;
 import com.android.systemui.plugins.ClockConfig;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.res.R;
@@ -39,6 +44,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
+import java.lang.reflect.Field;
+
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidTestingRunner.class)
@@ -142,19 +149,7 @@
     }
 
     @Test
-    public void onInit_addsOnLayoutChangeListenerToMediaHostContainer() {
-        when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
-                mMediaHostContainer);
-
-        mController.onInit();
-
-        ArgumentCaptor<View.OnLayoutChangeListener> captor =
-                ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
-        verify(mMediaHostContainer).addOnLayoutChangeListener(captor.capture());
-    }
-
-    @Test
-    public void clockSwitchHeightChanged_mediaChangingLayoutTransitionEnabled() {
+    public void clockSwitchHeightChanged_animatesMediaHostContainer() {
         when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
                 mMediaHostContainer);
 
@@ -167,6 +162,10 @@
         // Above here is the same as `onInit_addsOnLayoutChangeListenerToClockSwitch`.
         // Below here is the actual test.
 
+        ViewHierarchyAnimator.Companion animator = ViewHierarchyAnimator.Companion;
+        ViewHierarchyAnimator.Companion spiedAnimator = spy(animator);
+        setCompanion(spiedAnimator);
+
         View.OnLayoutChangeListener listener = captor.getValue();
 
         mController.setSplitShadeEnabled(true);
@@ -174,17 +173,20 @@
         when(mKeyguardUpdateMonitor.isKeyguardVisible()).thenReturn(true);
         when(mMediaHostContainer.getVisibility()).thenReturn(View.VISIBLE);
         when(mMediaHostContainer.getHeight()).thenReturn(200);
-        when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition);
 
         when(mKeyguardClockSwitch.getHeight()).thenReturn(0);
         listener.onLayoutChange(mKeyguardClockSwitch, /* left= */ 0, /* top= */ 0, /* right= */
                 0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */
                 0, /* oldBottom = */ 200);
-        verify(mMediaLayoutTransition).enableTransitionType(LayoutTransition.CHANGING);
+        verify(spiedAnimator).animateNextUpdate(mMediaHostContainer,
+                Interpolators.STANDARD, /* duration= */ 500L, /* animateChildren= */ false);
+
+        // Resets ViewHierarchyAnimator.Companion to its original value
+        setCompanion(animator);
     }
 
     @Test
-    public void clockSwitchHeightNotChanged_mediaChangingLayoutTransitionNotEnabled() {
+    public void clockSwitchHeightNotChanged_doesNotAnimateMediaOutputContainer() {
         when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
                 mMediaHostContainer);
 
@@ -197,6 +199,10 @@
         // Above here is the same as `onInit_addsOnLayoutChangeListenerToClockSwitch`.
         // Below here is the actual test.
 
+        ViewHierarchyAnimator.Companion animator = ViewHierarchyAnimator.Companion;
+        ViewHierarchyAnimator.Companion spiedAnimator = spy(animator);
+        setCompanion(spiedAnimator);
+
         View.OnLayoutChangeListener listener = captor.getValue();
 
         mController.setSplitShadeEnabled(true);
@@ -204,36 +210,24 @@
         when(mKeyguardUpdateMonitor.isKeyguardVisible()).thenReturn(true);
         when(mMediaHostContainer.getVisibility()).thenReturn(View.VISIBLE);
         when(mMediaHostContainer.getHeight()).thenReturn(200);
-        when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition);
 
         when(mKeyguardClockSwitch.getHeight()).thenReturn(200);
         listener.onLayoutChange(mKeyguardClockSwitch, /* left= */ 0, /* top= */ 0, /* right= */
                 0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */
                 0, /* oldBottom = */ 200);
-        verify(mMediaLayoutTransition, never()).enableTransitionType(LayoutTransition.CHANGING);
+        verify(spiedAnimator, never()).animateNextUpdate(any(), any(), anyLong(), anyBoolean());
+
+        // Resets ViewHierarchyAnimator.Companion to its original value
+        setCompanion(animator);
     }
 
-    @Test
-    public void onMediaHostContainerLayout_disablesChangingLayoutTransition() {
-        when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
-                mMediaHostContainer);
-
-        mController.onInit();
-
-        ArgumentCaptor<View.OnLayoutChangeListener> captor =
-                ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
-        verify(mMediaHostContainer).addOnLayoutChangeListener(captor.capture());
-
-        // Above here is the same as `onInit_addsOnLayoutChangeListenerToMediaHostContainer`.
-        // Below here is the actual test.
-
-        View.OnLayoutChangeListener listener = captor.getValue();
-
-        when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition);
-
-        when(mMediaLayoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGING)).thenReturn(
-                true);
-        listener.onLayoutChange(mMediaHostContainer, 1, 2, 3, 4, 1, 2, 3, 4);
-        verify(mMediaLayoutTransition).disableTransitionType(LayoutTransition.CHANGING);
+    private void setCompanion(ViewHierarchyAnimator.Companion companion) {
+        try {
+            Field field = ViewHierarchyAnimator.class.getDeclaredField("Companion");
+            field.setAccessible(true);
+            field.set(null, companion);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index 58d372c..86439e5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -1,6 +1,5 @@
 package com.android.keyguard
 
-import android.animation.LayoutTransition
 import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
@@ -36,20 +35,6 @@
     }
 
     @Test
-    fun mediaViewHasLayoutTransitionInDisabledState() {
-        val layoutTransition = (mediaView as ViewGroup).layoutTransition
-        assertThat(layoutTransition).isNotNull()
-        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGE_APPEARING))
-            .isFalse()
-        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGE_DISAPPEARING))
-            .isFalse()
-        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.APPEARING)).isFalse()
-        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.DISAPPEARING))
-            .isFalse()
-        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGING)).isFalse()
-    }
-
-    @Test
     fun setChildrenTranslationYExcludingMediaView_mediaViewIsNotTranslated() {
         val translationY = 1234f
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index 3da7261..5346db1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -19,6 +19,9 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
 
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -87,6 +90,7 @@
 
     @Before
     public void setUp() throws Exception {
+        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
         MockitoAnnotations.initMocks(this);
         mContextWrapper = new ContextWrapper(mContext) {
             @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
index fd258e3..3b2ea0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -56,6 +59,7 @@
 
     @Before
     public void setUp() throws Exception {
+        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
                 mock(SecureSettings.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 3a8bcd0..76a3153 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.any;
@@ -75,6 +78,7 @@
 
     @Before
     public void setUp() throws Exception {
+        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
                 stubWindowManager);
@@ -96,6 +100,7 @@
         Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
                 mLastIsMoveToTucked);
         mEndListenerCaptor.getAllValues().clear();
+        mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
index 9b81947..83bcd8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.res.Resources;
@@ -43,6 +46,7 @@
 
     @Before
     public void setUp() throws Exception {
+        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
         mMenuViewAppearance = new MenuViewAppearance(mContext, windowManager);
         mMenuEduTooltipView = new MenuEduTooltipView(mContext, mMenuViewAppearance);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 5764839..e01f1b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -19,6 +19,9 @@
 import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
 import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
 
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.doReturn;
@@ -72,6 +75,7 @@
 
     @Before
     public void setUp() {
+        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
                 stubWindowManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index 98be49f..a88ee10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -18,6 +18,9 @@
 
 import static android.view.View.OVER_SCROLL_NEVER;
 
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyFloat;
@@ -33,6 +36,7 @@
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.test.filters.SmallTest;
 
@@ -79,6 +83,7 @@
 
     @Before
     public void setUp() throws Exception {
+        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
                 mock(SecureSettings.class));
@@ -213,5 +218,6 @@
     @After
     public void tearDown() {
         mMotionEventHelper.recycleEvents();
+        mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
index 31824ec..41e5c20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
@@ -19,6 +19,9 @@
 import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.systemBars;
 
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
@@ -73,6 +76,7 @@
 
     @Before
     public void setUp() throws Exception {
+        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
         final WindowManager wm = mContext.getSystemService(WindowManager.class);
         doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
                 mWindowManager).getMaximumWindowMetrics();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 5bb5e01..b0776c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -22,7 +22,9 @@
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.systemBars;
 
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
 import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -50,6 +52,7 @@
 import android.view.WindowMetrics;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -110,6 +113,7 @@
 
     @Before
     public void setUp() throws Exception {
+        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
         final Rect mDisplayBounds = new Rect();
         mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH,
                 DISPLAY_WINDOW_HEIGHT);
@@ -145,6 +149,7 @@
                 UserHandle.USER_CURRENT);
 
         mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
+        mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel);
         mMenuViewLayer.onDetachedFromWindow();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index 5cd0fd0..ac2bfaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -18,6 +18,9 @@
 
 import static android.app.UiModeManager.MODE_NIGHT_YES;
 
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
@@ -67,6 +70,7 @@
 
     @Before
     public void setUp() throws Exception {
+        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
         mNightMode = mUiModeManager.getNightMode();
         mUiModeManager.setNightMode(MODE_NIGHT_YES);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
index a7e7dd0..2b51ac5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
@@ -19,6 +19,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import androidx.core.animation.doOnEnd
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.doOnEnd
@@ -30,6 +31,7 @@
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 @RunWithLooper
+@FlakyTest(bugId = 302149604)
 class AnimatorTestRuleOrderTest : SysuiTestCase() {
 
     @get:Rule val animatorTestRule = AnimatorTestRule()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index e9e9624..ebe13fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -29,7 +29,6 @@
 import android.view.LayoutInflater
 import android.view.MotionEvent
 import android.view.Surface
-import android.view.Surface.ROTATION_0
 import android.view.Surface.Rotation
 import android.view.View
 import android.view.WindowManager
@@ -45,7 +44,6 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -141,7 +139,6 @@
     ) {
         controllerOverlay = UdfpsControllerOverlay(
             context,
-            fingerprintManager,
             inflater,
             windowManager,
             accessibilityManager,
@@ -155,7 +152,6 @@
             keyguardStateController,
             unlockedScreenOffAnimationController,
             udfpsDisplayMode,
-            secureSettings,
             REQUEST_ID,
             reason,
             controllerCallback,
@@ -165,7 +161,6 @@
             primaryBouncerInteractor,
             alternateBouncerInteractor,
             isDebuggable,
-            udfpsUtils,
             udfpsKeyguardAccessibilityDelegate,
             udfpsKeyguardViewModels,
         )
@@ -212,8 +207,8 @@
             val lp = layoutParamsCaptor.value
             assertThat(lp.x).isEqualTo(0)
             assertThat(lp.y).isEqualTo(0)
-            assertThat(lp.width).isEqualTo(SENSOR_WIDTH)
-            assertThat(lp.height).isEqualTo(SENSOR_HEIGHT)
+            assertThat(lp.width).isEqualTo(DISPLAY_WIDTH)
+            assertThat(lp.height).isEqualTo(DISPLAY_HEIGHT)
         }
     }
 
@@ -230,8 +225,8 @@
             val lp = layoutParamsCaptor.value
             assertThat(lp.x).isEqualTo(0)
             assertThat(lp.y).isEqualTo(0)
-            assertThat(lp.width).isEqualTo(SENSOR_WIDTH)
-            assertThat(lp.height).isEqualTo(SENSOR_HEIGHT)
+            assertThat(lp.width).isEqualTo(DISPLAY_WIDTH)
+            assertThat(lp.height).isEqualTo(DISPLAY_HEIGHT)
         }
     }
 
@@ -247,9 +242,9 @@
             // Sensor should be in the bottom left corner in ROTATION_90.
             val lp = layoutParamsCaptor.value
             assertThat(lp.x).isEqualTo(0)
-            assertThat(lp.y).isEqualTo(DISPLAY_WIDTH - SENSOR_WIDTH)
-            assertThat(lp.width).isEqualTo(SENSOR_HEIGHT)
-            assertThat(lp.height).isEqualTo(SENSOR_WIDTH)
+            assertThat(lp.y).isEqualTo(0)
+            assertThat(lp.width).isEqualTo(DISPLAY_HEIGHT)
+            assertThat(lp.height).isEqualTo(DISPLAY_WIDTH)
         }
     }
 
@@ -264,10 +259,10 @@
 
             // Sensor should be in the top right corner in ROTATION_270.
             val lp = layoutParamsCaptor.value
-            assertThat(lp.x).isEqualTo(DISPLAY_HEIGHT - SENSOR_HEIGHT)
+            assertThat(lp.x).isEqualTo(0)
             assertThat(lp.y).isEqualTo(0)
-            assertThat(lp.width).isEqualTo(SENSOR_HEIGHT)
-            assertThat(lp.height).isEqualTo(SENSOR_WIDTH)
+            assertThat(lp.width).isEqualTo(DISPLAY_HEIGHT)
+            assertThat(lp.height).isEqualTo(DISPLAY_WIDTH)
         }
     }
 
@@ -345,11 +340,10 @@
     }
 
     @Test
-    fun smallOverlayOnEnrollmentWithA11y() = withRotation(ROTATION_0) {
+    fun smallOverlayOnEnrollmentWithA11y() = withRotation(Surface.ROTATION_0) {
         withReason(REASON_ENROLL_ENROLLING) {
             // When a11y enabled during enrollment
             whenever(accessibilityManager.isTouchExplorationEnabled).thenReturn(true)
-            whenever(featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true)
 
             controllerOverlay.show(udfpsController, overlayParams)
             verify(windowManager).addView(
@@ -363,22 +357,4 @@
             assertThat(lp.height).isEqualTo(overlayParams.sensorBounds.height())
         }
     }
-
-    @Test
-    fun fullScreenOverlayWithNewTouchDetectionEnabled() = withRotation(ROTATION_0) {
-        withReason(REASON_AUTH_KEYGUARD) {
-            whenever(featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true)
-
-            controllerOverlay.show(udfpsController, overlayParams)
-            verify(windowManager).addView(
-                    eq(controllerOverlay.overlayView),
-                    layoutParamsCaptor.capture()
-            )
-
-            // Layout params should use natural display width and height
-            val lp = layoutParamsCaptor.value
-            assertThat(lp.width).isEqualTo(overlayParams.naturalDisplayWidth)
-            assertThat(lp.height).isEqualTo(overlayParams.naturalDisplayHeight)
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index a36f4e9..dcb5398 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -88,7 +88,6 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
@@ -105,7 +104,6 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecution;
 import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.util.time.SystemClock;
 
@@ -122,7 +120,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 
 import javax.inject.Provider;
 
@@ -206,8 +203,6 @@
     @Mock
     private ActivityLaunchAnimator mActivityLaunchAnimator;
     @Mock
-    private AlternateUdfpsTouchProvider mAlternateTouchProvider;
-    @Mock
     private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     @Mock
     private SinglePointerTouchProcessor mSinglePointerTouchProcessor;
@@ -216,8 +211,6 @@
     @Mock
     private AlternateBouncerInteractor mAlternateBouncerInteractor;
     @Mock
-    private SecureSettings mSecureSettings;
-    @Mock
     private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
     @Mock
     private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
@@ -239,7 +232,6 @@
     private ScreenLifecycle.Observer mScreenObserver;
     private FingerprintSensorPropertiesInternal mOpticalProps;
     private FingerprintSensorPropertiesInternal mUltrasonicProps;
-    private UdfpsUtils mUdfpsUtils;
     @Mock
     private InputManager mInputManager;
     @Mock
@@ -250,8 +242,6 @@
         mContext.getOrCreateTestableResources()
                 .addOverride(com.android.internal.R.bool.config_ignoreUdfpsVote, false);
 
-        mUdfpsUtils = new UdfpsUtils();
-
         when(mLayoutInflater.inflate(R.layout.udfps_view, null, false))
                 .thenReturn(mUdfpsView);
         when(mLayoutInflater.inflate(R.layout.udfps_keyguard_view_legacy, null))
@@ -292,24 +282,13 @@
         // Create a fake background executor.
         mBiometricExecutor = new FakeExecutor(new FakeSystemClock());
 
-        initUdfpsController(true /* hasAlternateTouchProvider */);
+        initUdfpsController(mOpticalProps);
     }
 
-
-    private void initUdfpsController(boolean hasAlternateTouchProvider) {
-        initUdfpsController(mOpticalProps, hasAlternateTouchProvider);
-    }
-
-    private void initUdfpsController(FingerprintSensorPropertiesInternal sensorProps,
-            boolean hasAlternateTouchProvider) {
+    private void initUdfpsController(FingerprintSensorPropertiesInternal sensorProps) {
         reset(mFingerprintManager);
         reset(mScreenLifecycle);
 
-        final Optional<Provider<AlternateUdfpsTouchProvider>> alternateTouchProvider =
-                hasAlternateTouchProvider ? Optional.of(
-                        (Provider<AlternateUdfpsTouchProvider>) () -> mAlternateTouchProvider)
-                        : Optional.empty();
-
         mUdfpsController = new UdfpsController(
                 mContext,
                 new FakeExecution(),
@@ -339,15 +318,12 @@
                 mSystemUIDialogManager,
                 mLatencyTracker,
                 mActivityLaunchAnimator,
-                alternateTouchProvider,
                 mBiometricExecutor,
                 mPrimaryBouncerInteractor,
                 mSinglePointerTouchProcessor,
                 mSessionTracker,
                 mAlternateBouncerInteractor,
-                mSecureSettings,
                 mInputManager,
-                mUdfpsUtils,
                 mock(KeyguardFaceAuthInteractor.class),
                 mUdfpsKeyguardAccessibilityDelegate,
                 mUdfpsKeyguardViewModels
@@ -374,17 +350,15 @@
     public void onActionDownTouch_whenCanDismissLockScreen_entersDevice() throws RemoteException {
         // GIVEN can dismiss lock screen and the current animation is an UdfpsKeyguardViewController
         when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
         when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
 
-        // GIVEN that the overlay is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false);
 
         // WHEN ACTION_DOWN is received
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
-        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.first);
+        MotionEvent downEvent = obtainMotionEvent(ACTION_DOWN, 0, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
         mBiometricExecutor.runAllReady();
         downEvent.recycle();
@@ -408,16 +382,14 @@
             throws RemoteException {
         // GIVEN can dismiss lock screen and the current animation is an UdfpsKeyguardViewController
         when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
         when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
 
-        // GIVEN that the overlay is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false);
 
         // WHEN ACTION_MOVE is received
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.first);
         MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
         if (stale) {
             mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId);
@@ -436,22 +408,22 @@
     public void onMultipleTouch_whenCanDismissLockScreen_entersDeviceOnce() throws RemoteException {
         // GIVEN can dismiss lock screen and the current animation is an UdfpsKeyguardViewController
         when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
         when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
 
-        // GIVEN that the overlay is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UNCHANGED, false);
 
-        // WHEN multiple touches are received
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        // GIVEN that the overlay is showing
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.first);
         MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
         mBiometricExecutor.runAllReady();
         downEvent.recycle();
+
         MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.second);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
         mBiometricExecutor.runAllReady();
         moveEvent.recycle();
@@ -593,22 +565,17 @@
 
     private static class TestParams {
         public final FingerprintSensorPropertiesInternal sensorProps;
-        public final boolean hasAlternateTouchProvider;
 
-        TestParams(FingerprintSensorPropertiesInternal sensorProps,
-                boolean hasAlternateTouchProvider) {
+        TestParams(FingerprintSensorPropertiesInternal sensorProps) {
             this.sensorProps = sensorProps;
-            this.hasAlternateTouchProvider = hasAlternateTouchProvider;
         }
     }
 
     private void runWithAllParams(ThrowingConsumer<TestParams> testParamsConsumer) {
         for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps,
                 mUltrasonicProps)) {
-            for (boolean hasAlternateTouchProvider : new boolean[]{false, true}) {
-                initUdfpsController(sensorProps, hasAlternateTouchProvider);
-                testParamsConsumer.accept(new TestParams(sensorProps, hasAlternateTouchProvider));
-            }
+            initUdfpsController(sensorProps);
+            testParamsConsumer.accept(new TestParams(sensorProps));
         }
     }
 
@@ -621,23 +588,33 @@
     private void onTouch_propagatesTouchInNativeOrientationAndResolutionParameterized(
             TestParams testParams) throws RemoteException {
         reset(mUdfpsView);
+        when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl);
 
         final Rect sensorBounds = new Rect(1000, 1900, 1080, 1920); // Bottom right corner.
+        final int pointerId = 0;
         final int displayWidth = 1080;
         final int displayHeight = 1920;
-        final float scaleFactor = 0.75f; // This means the native resolution is 1440x2560.
+        final float scaleFactor = 1f; // This means the native resolution is 1440x2560.
         final float touchMinor = 10f;
         final float touchMajor = 20f;
+        final float orientation = 30f;
 
         // Expecting a touch at the very bottom right corner in native orientation and resolution.
-        final int expectedX = (int) (displayWidth / scaleFactor);
-        final int expectedY = (int) (displayHeight / scaleFactor);
+        final float expectedX = displayWidth / scaleFactor;
+        final float expectedY = displayHeight / scaleFactor;
         final float expectedMinor = touchMinor / scaleFactor;
         final float expectedMajor = touchMajor / scaleFactor;
 
         // Configure UdfpsView to accept the ACTION_DOWN event
         when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+        // GIVEN a valid touch on sensor
+        NormalizedTouchData touchData = new NormalizedTouchData(pointerId, displayWidth,
+                displayHeight, touchMinor, touchMajor, orientation, 0L, 0L);
+        TouchProcessorResult processorDownResult = new TouchProcessorResult.ProcessedTouch(
+                InteractionEvent.DOWN, 1, touchData);
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorDownResult);
 
         // Show the overlay.
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
@@ -654,21 +631,12 @@
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricExecutor.runAllReady();
         event.recycle();
-        event = obtainMotionEvent(ACTION_MOVE, displayWidth, displayHeight, touchMinor, touchMajor);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
-        mBiometricExecutor.runAllReady();
-        event.recycle();
-        if (testParams.hasAlternateTouchProvider) {
-            verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
-                    eq(expectedY), eq(expectedMinor), eq(expectedMajor));
-        } else {
-            verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
-                    eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
-                    eq(expectedMinor), eq(expectedMajor));
-        }
+        verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+                eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY),
+                eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(),
+                anyBoolean());
 
         // Test ROTATION_90
-        reset(mAlternateTouchProvider);
         reset(mFingerprintManager);
         mUdfpsController.updateOverlayParams(testParams.sensorProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
@@ -677,21 +645,12 @@
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricExecutor.runAllReady();
         event.recycle();
-        event = obtainMotionEvent(ACTION_MOVE, displayHeight, 0, touchMinor, touchMajor);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
-        mBiometricExecutor.runAllReady();
-        event.recycle();
-        if (testParams.hasAlternateTouchProvider) {
-            verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
-                    eq(expectedY), eq(expectedMinor), eq(expectedMajor));
-        } else {
-            verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
-                    eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
-                    eq(expectedMinor), eq(expectedMajor));
-        }
+        verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+                eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY),
+                eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(),
+                anyBoolean());
 
         // Test ROTATION_270
-        reset(mAlternateTouchProvider);
         reset(mFingerprintManager);
         mUdfpsController.updateOverlayParams(testParams.sensorProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
@@ -700,21 +659,12 @@
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricExecutor.runAllReady();
         event.recycle();
-        event = obtainMotionEvent(ACTION_MOVE, 0, displayWidth, touchMinor, touchMajor);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
-        mBiometricExecutor.runAllReady();
-        event.recycle();
-        if (testParams.hasAlternateTouchProvider) {
-            verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
-                    eq(expectedY), eq(expectedMinor), eq(expectedMajor));
-        } else {
-            verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
-                    eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
-                    eq(expectedMinor), eq(expectedMajor));
-        }
+        verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+                eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY),
+                eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(),
+                anyBoolean());
 
         // Test ROTATION_180
-        reset(mAlternateTouchProvider);
         reset(mFingerprintManager);
         mUdfpsController.updateOverlayParams(testParams.sensorProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
@@ -724,18 +674,10 @@
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricExecutor.runAllReady();
         event.recycle();
-        event = obtainMotionEvent(ACTION_MOVE, displayWidth, displayHeight, touchMinor, touchMajor);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
-        mBiometricExecutor.runAllReady();
-        event.recycle();
-        if (testParams.hasAlternateTouchProvider) {
-            verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
-                    eq(expectedY), eq(expectedMinor), eq(expectedMajor));
-        } else {
-            verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
-                    eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
-                    eq(expectedMinor), eq(expectedMajor));
-        }
+        verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+                eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY),
+                eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(),
+                anyBoolean());
     }
 
     @Test
@@ -744,46 +686,36 @@
     }
 
     private void fingerDownParameterized(TestParams testParams) throws RemoteException {
-        reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mLatencyTracker,
+        reset(mUdfpsView, mFingerprintManager, mLatencyTracker,
                 mKeyguardUpdateMonitor);
+        when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl);
 
         // Configure UdfpsView to accept the ACTION_DOWN event
         when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
         when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
 
-        // GIVEN that the overlay is showing
+        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+                0L);
+        final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch(
+                InteractionEvent.DOWN, 1 /* pointerId */, touchData);
+
+        initUdfpsController(testParams.sensorProps);
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
 
         // WHEN ACTION_DOWN is received
+        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorResultDown);
         MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
         mBiometricExecutor.runAllReady();
         downEvent.recycle();
 
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
-        mBiometricExecutor.runAllReady();
-        moveEvent.recycle();
-
-        mFgExecutor.runAllReady();
-
         // THEN the touch provider is notified about onPointerDown.
-        if (testParams.hasAlternateTouchProvider) {
-            verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(0f),
-                    eq(0f));
-            verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(),
-                    anyInt(), anyFloat(), anyFloat());
-            verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
-        } else {
-            verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
-                    eq(testParams.sensorProps.sensorId), eq(0), eq(0), eq(0f), eq(0f));
-            verify(mAlternateTouchProvider, never()).onPointerDown(anyInt(), anyInt(), anyInt(),
-                    anyFloat(), anyFloat());
-        }
+        verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(),
+                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
 
         // AND display configuration begins
         if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
@@ -800,33 +732,20 @@
             // AND onDisplayConfigured notifies FingerprintManager about onUiReady
             mOnDisplayConfiguredCaptor.getValue().run();
             mBiometricExecutor.runAllReady();
-            if (testParams.hasAlternateTouchProvider) {
-                InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker);
-                inOrder.verify(mAlternateTouchProvider).onUiReady();
-                inOrder.verify(mLatencyTracker).onActionEnd(
-                        eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
-                verify(mFingerprintManager, never()).onUdfpsUiEvent(
-                        eq(FingerprintManager.UDFPS_UI_READY), anyLong(), anyInt());
-            } else {
-                InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker);
-                inOrder.verify(mFingerprintManager).onUdfpsUiEvent(
-                        eq(FingerprintManager.UDFPS_UI_READY), eq(TEST_REQUEST_ID),
-                        eq(testParams.sensorProps.sensorId));
-                inOrder.verify(mLatencyTracker).onActionEnd(
-                        eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
-                verify(mAlternateTouchProvider, never()).onUiReady();
-            }
+            InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker);
+            inOrder.verify(mFingerprintManager).onUdfpsUiEvent(
+                    eq(FingerprintManager.UDFPS_UI_READY), eq(TEST_REQUEST_ID),
+                    eq(testParams.sensorProps.sensorId));
+            inOrder.verify(mLatencyTracker).onActionEnd(
+                    eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
         } else {
             verify(mFingerprintManager, never()).onUdfpsUiEvent(
                     eq(FingerprintManager.UDFPS_UI_READY), anyLong(), anyInt());
-            verify(mAlternateTouchProvider, never()).onUiReady();
             verify(mLatencyTracker, never()).onActionEnd(
                     eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
         }
     }
 
-
-
     @Test
     public void aodInterrupt() {
         runWithAllParams(this::aodInterruptParameterized);
@@ -834,8 +753,9 @@
 
     private void aodInterruptParameterized(TestParams testParams) throws RemoteException {
         mUdfpsController.cancelAodSendFingerUpAction();
-        reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mKeyguardUpdateMonitor);
+        reset(mUdfpsView, mFingerprintManager, mKeyguardUpdateMonitor);
         when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+        when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl);
 
         // GIVEN that the overlay is showing and screen is on and fp is running
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
@@ -855,19 +775,8 @@
         }
         mBiometricExecutor.runAllReady();
 
-        if (testParams.hasAlternateTouchProvider) {
-            verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0),
-                    eq(3f) /* minor */, eq(2f) /* major */);
-            verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(),
-                    anyInt(), anyFloat(), anyFloat());
-            verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
-        } else {
-            verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
-                    eq(testParams.sensorProps.sensorId), eq(0), eq(0), eq(3f) /* minor */,
-                    eq(2f) /* major */);
-            verify(mAlternateTouchProvider, never()).onPointerDown(anyLong(), anyInt(), anyInt(),
-                    anyFloat(), anyFloat());
-        }
+        verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(),
+                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
     }
 
     @Test
@@ -907,10 +816,12 @@
     private void onFingerUp_displayConfigurationParameterized(TestParams testParams)
             throws RemoteException {
         reset(mUdfpsView);
+        when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl);
+
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false);
 
         // GIVEN AOD interrupt
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
@@ -918,7 +829,8 @@
             when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
 
             // WHEN up-action received
-            verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+            when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                    touchProcessorResult.second);
             MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0);
             mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
             mBiometricExecutor.runAllReady();
@@ -931,7 +843,8 @@
             when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
 
             // WHEN up-action received
-            verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+            when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                    touchProcessorResult.second);
             MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0);
             mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
             mBiometricExecutor.runAllReady();
@@ -1015,16 +928,19 @@
     private void aodInterruptCancelTimeoutActionOnFingerUpParameterized(TestParams testParams)
             throws RemoteException {
         reset(mUdfpsView);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+        when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl);
 
         // GIVEN AOD interrupt
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
         mFgExecutor.runAllReady();
 
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false);
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+
         if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
             // Configure UdfpsView to accept the ACTION_UP event
             when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
@@ -1033,7 +949,8 @@
         }
 
         // WHEN ACTION_UP is received
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.second);
         MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
         mBiometricExecutor.runAllReady();
@@ -1043,16 +960,13 @@
         when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
 
         // WHEN ACTION_DOWN is received
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.first);
         MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
         mBiometricExecutor.runAllReady();
         downEvent.recycle();
 
-        // WHEN ACTION_MOVE is received
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
-        mBiometricExecutor.runAllReady();
-        moveEvent.recycle();
         mFgExecutor.runAllReady();
 
         if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
@@ -1122,24 +1036,16 @@
 
     @Test
     public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled() throws RemoteException {
-        // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-
-        // GIVEN that the overlay is showing and a11y touch exploration enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, true);
 
         // WHEN ACTION_HOVER is received
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.first);
         verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture());
         MotionEvent enterEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0);
         mHoverListenerCaptor.getValue().onHover(mUdfpsView, enterEvent);
         enterEvent.recycle();
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0);
-        mHoverListenerCaptor.getValue().onHover(mUdfpsView, moveEvent);
-        moveEvent.recycle();
 
         // THEN tick haptic is played
         verify(mVibrator).vibrate(
@@ -1159,24 +1065,16 @@
     public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled_oneWayHapticsEnabled()
             throws RemoteException {
         when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true);
-        // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
 
-        // GIVEN that the overlay is showing and a11y touch exploration enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, true);
 
         // WHEN ACTION_HOVER is received
-        verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture());
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.first);
         MotionEvent enterEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0);
         mHoverListenerCaptor.getValue().onHover(mUdfpsView, enterEvent);
         enterEvent.recycle();
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0);
-        mHoverListenerCaptor.getValue().onHover(mUdfpsView, moveEvent);
-        moveEvent.recycle();
 
         // THEN context click haptic is played
         verify(mVibrator).performHapticFeedback(
@@ -1187,26 +1085,16 @@
 
     @Test
     public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled() throws RemoteException {
-        // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-
-        // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false);
 
         // WHEN ACTION_DOWN is received
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.first);
         MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
         mBiometricExecutor.runAllReady();
         downEvent.recycle();
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
-        mBiometricExecutor.runAllReady();
-        moveEvent.recycle();
 
         // THEN NO haptic played
         verify(mVibrator, never()).vibrate(
@@ -1221,80 +1109,26 @@
     public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled__oneWayHapticsEnabled()
             throws RemoteException {
         when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true);
-        // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
 
-        // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false);
 
         // WHEN ACTION_DOWN is received
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                touchProcessorResult.first);
         MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
         mBiometricExecutor.runAllReady();
         downEvent.recycle();
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
-        mBiometricExecutor.runAllReady();
-        moveEvent.recycle();
 
         // THEN NO haptic played
         verify(mVibrator, never()).performHapticFeedback(any(), anyInt());
     }
 
     @Test
-    public void onTouch_withoutNewTouchDetection_shouldCallOldFingerprintManagerPath()
-            throws RemoteException {
-        // Disable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(false);
-
-        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
-
-        // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-
-        // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
-
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
-
-        // WHEN ACTION_DOWN is received
-        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
-        mBiometricExecutor.runAllReady();
-        downEvent.recycle();
-
-        // AND ACTION_MOVE is received
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
-        mBiometricExecutor.runAllReady();
-        moveEvent.recycle();
-
-        // AND ACTION_UP is received
-        MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
-        mBiometricExecutor.runAllReady();
-        upEvent.recycle();
-
-        // THEN the old FingerprintManager path is invoked.
-        verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(),
-                anyFloat(), anyFloat());
-        verify(mFingerprintManager).onPointerUp(anyLong(), anyInt());
-    }
-
-    @Test
     public void fingerDown_falsingManagerInformed() throws RemoteException {
         final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
-                givenAcceptFingerDownEvent();
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false);
 
         // WHEN ACTION_DOWN is received
         when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
@@ -1308,85 +1142,46 @@
         verify(mFalsingManager).isFalseTouch(UDFPS_AUTHENTICATION);
     }
 
-    @Test
-    public void onTouch_withNewTouchDetection_shouldCallNewFingerprintManagerPath()
-            throws RemoteException {
-        final Pair<TouchProcessorResult, TouchProcessorResult> processorResultDownAndUp =
-                givenAcceptFingerDownEvent();
-
-        // WHEN ACTION_DOWN is received
-        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
-                processorResultDownAndUp.first);
-        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
-        mBiometricExecutor.runAllReady();
-        downEvent.recycle();
-
-        // AND ACTION_UP is received
-        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
-                processorResultDownAndUp.second);
-        MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
-        mBiometricExecutor.runAllReady();
-        upEvent.recycle();
-
-        // THEN the new FingerprintManager path is invoked.
-        verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(),
-                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
-        verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(),
-                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
-    }
-
-    private Pair<TouchProcessorResult, TouchProcessorResult> givenAcceptFingerDownEvent()
+    private Pair<TouchProcessorResult, TouchProcessorResult> givenFingerEvent(
+            InteractionEvent event1, InteractionEvent event2, boolean a11y)
             throws RemoteException {
         final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
                 0L);
         final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch(
-                InteractionEvent.DOWN, 1 /* pointerId */, touchData);
+                event1, 1 /* pointerId */, touchData);
         final TouchProcessorResult processorResultUp = new TouchProcessorResult.ProcessedTouch(
-                InteractionEvent.UP, 1 /* pointerId */, touchData);
-
-        // Enable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
+                event2, 1 /* pointerId */, touchData);
 
         // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
+        initUdfpsController(mOpticalProps);
 
         // Configure UdfpsView to accept the ACTION_DOWN event
         when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
 
         // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(a11y);
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        if (a11y) {
+            verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture());
+        } else {
+            verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        }
 
         return new Pair<>(processorResultDown, processorResultUp);
     }
 
     @Test
-    public void onTouch_WithNewTouchDetection_forwardToKeyguard() throws RemoteException {
+    public void onTouch_forwardToKeyguard() throws RemoteException {
         final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
                 0L);
         final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch(
                 InteractionEvent.UNCHANGED, -1 /* pointerOnSensorId */, touchData);
 
-        // Enable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
-
-        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
-
-        // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false);
-
         // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
         when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
@@ -1402,47 +1197,23 @@
 
         // THEN the touch is forwarded to Keyguard
         verify(mStatusBarKeyguardViewManager).onTouch(downEvent);
-        downEvent.recycle();
     }
 
     @Test
-    public void onTouch_withNewTouchDetection_pilferPointer() throws RemoteException {
-        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
-                0L);
-        final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch(
-                InteractionEvent.DOWN, 1 /* pointerId */, touchData);
-
-        // Enable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
-
-        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
-
-        // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-
-        // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
-
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+    public void onTouch_pilferPointer() throws RemoteException {
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UNCHANGED, false);
 
         // WHEN ACTION_DOWN is received
         when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
-                processorResultDown);
+                touchProcessorResult.first);
         MotionEvent event = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricExecutor.runAllReady();
 
         // WHEN ACTION_MOVE is received after
-        final TouchProcessorResult processorResultUnchanged =
-                new TouchProcessorResult.ProcessedTouch(
-                        InteractionEvent.UNCHANGED, 1 /* pointerId */, touchData);
         when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
-                processorResultUnchanged);
+                touchProcessorResult.second);
         event.setAction(ACTION_MOVE);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricExecutor.runAllReady();
@@ -1453,25 +1224,13 @@
     }
 
     @Test
-    public void onTouch_withNewTouchDetection_doNotPilferPointer() throws RemoteException {
+    public void onTouch_doNotPilferPointer() throws RemoteException {
         final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
                 0L);
         final TouchProcessorResult processorResultUnchanged =
                 new TouchProcessorResult.ProcessedTouch(InteractionEvent.UNCHANGED,
-                        1 /* pointerId */, touchData);
+                        -1 /* pointerId */, touchData);
 
-        // Enable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
-
-        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
-
-        // Configure UdfpsView to not accept the ACTION_DOWN event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false);
-
-        // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
@@ -1491,36 +1250,17 @@
     }
 
     @Test
-    public void onTouch_withNewTouchDetection_pilferPointerWhenAltBouncerShowing()
+    public void onTouch_pilferPointerWhenAltBouncerShowing()
             throws RemoteException {
-        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
-                0L);
-        final TouchProcessorResult processorResultUnchanged =
-                new TouchProcessorResult.ProcessedTouch(InteractionEvent.UNCHANGED,
-                        1 /* pointerId */, touchData);
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.UNCHANGED, InteractionEvent.UP, false);
 
-        // Enable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
-
-        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
-
-        // Configure UdfpsView to not accept the ACTION_DOWN event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false);
-
-        // GIVEN that the alternate bouncer is showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+        // WHEN alternate bouncer is showing
         when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
-
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
 
         // WHEN ACTION_DOWN is received and touch is not within sensor
         when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
-                processorResultUnchanged);
+                touchProcessorResult.first);
         MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
         mBiometricExecutor.runAllReady();
@@ -1531,32 +1271,10 @@
     }
 
     @Test
-    public void onTouch_withNewTouchDetection_doNotProcessTouchWhenPullingUpBouncer()
+    public void onTouch_doNotProcessTouchWhenPullingUpBouncer()
             throws RemoteException {
-        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
-                0L);
-        final TouchProcessorResult processorResultMove =
-                new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN,
-                        1 /* pointerId */, touchData);
-
-        // Enable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
-
-        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
-
-        // Configure UdfpsView to accept the ACTION_MOVE event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-
-        // GIVEN that the alternate bouncer is not showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
-        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
-
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.UNCHANGED, InteractionEvent.UP, false);
 
         // GIVEN a swipe up to bring up primary bouncer is in progress or swipe down for QS
         when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true);
@@ -1564,7 +1282,7 @@
 
         // WHEN ACTION_MOVE is received and touch is within sensor
         when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
-                processorResultMove);
+                touchProcessorResult.first);
         MotionEvent moveEvent = MotionEvent.obtain(0, 0, ACTION_MOVE, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
         mBiometricExecutor.runAllReady();
@@ -1578,40 +1296,19 @@
 
 
     @Test
-    public void onTouch_withNewTouchDetection_qsDrag_processesTouchWhenAlternateBouncerVisible()
+    public void onTouch_qsDrag_processesTouchWhenAlternateBouncerVisible()
             throws RemoteException {
-        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
-                0L);
-        final TouchProcessorResult processorResultMove =
-                new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN,
-                        1 /* pointerId */, touchData);
+        final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+                givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false);
 
-        // Enable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
-
-        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
-
-        // Configure UdfpsView to accept the ACTION_MOVE event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-
-        // GIVEN that the alternate bouncer is showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
         when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
-
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
-
         // GIVEN swipe down for QS
         when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(false);
         when(mLockscreenShadeTransitionController.getQSDragProgress()).thenReturn(1f);
 
         // WHEN ACTION_MOVE is received and touch is within sensor
         when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
-                processorResultMove);
+                touchProcessorResult.first);
         MotionEvent moveEvent = MotionEvent.obtain(0, 0, ACTION_MOVE, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
         mBiometricExecutor.runAllReady();
@@ -1689,43 +1386,4 @@
         // THEN vibrate is used
         verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS));
     }
-
-    @Test
-    public void aodInterrupt_withNewTouchDetection() throws RemoteException {
-        mUdfpsController.cancelAodSendFingerUpAction();
-        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
-                0L);
-        final TouchProcessorResult processorResultDown =
-                new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN,
-                        1 /* pointerId */, touchData);
-
-        // Enable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
-
-        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
-
-        // GIVEN that the overlay is showing and screen is on and fp is running
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, 0,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mScreenObserver.onScreenTurnedOn();
-        mFgExecutor.runAllReady();
-
-        // WHEN fingerprint is requested because of AOD interrupt
-        mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
-
-        // Check case where touch driver sends touch to UdfpsView as well
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
-                processorResultDown);
-        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
-
-        mBiometricExecutor.runAllReady();
-
-        // THEN only one onPointerDown is sent
-        verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(),
-                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index 3276e66..e512adc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -30,7 +30,6 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -116,7 +115,7 @@
     }
 
     public UdfpsKeyguardViewControllerLegacy createUdfpsKeyguardViewController() {
-        return createUdfpsKeyguardViewController(false, false);
+        return createUdfpsKeyguardViewController(false);
     }
 
     public void captureKeyGuardViewManagerCallback() {
@@ -126,8 +125,7 @@
     }
 
     protected UdfpsKeyguardViewControllerLegacy createUdfpsKeyguardViewController(
-            boolean useModernBouncer, boolean useExpandedOverlay) {
-        mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, useExpandedOverlay);
+            boolean useModernBouncer) {
         UdfpsKeyguardViewControllerLegacy controller = new UdfpsKeyguardViewControllerLegacy(
                 mView,
                 mStatusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
index b018a3e..21928cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
@@ -18,15 +18,12 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.testing.TestableLooper;
-import android.view.MotionEvent;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -44,8 +41,7 @@
         UdfpsKeyguardViewLegacyControllerBaseTest {
     @Override
     public UdfpsKeyguardViewControllerLegacy createUdfpsKeyguardViewController() {
-        return createUdfpsKeyguardViewController(/* useModernBouncer */ false,
-                /* useExpandedOverlay */ false);
+        return createUdfpsKeyguardViewController(/* useModernBouncer */ false);
     }
 
     @Test
@@ -216,37 +212,4 @@
         sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED);
         assertTrue(mController.shouldPauseAuth());
     }
-
-    @Test
-    // TODO(b/259264861): Tracking Bug
-    public void testUdfpsExpandedOverlayOn() {
-        // GIVEN view is attached and useExpandedOverlay is true
-        mController = createUdfpsKeyguardViewController(false, true);
-        mController.onViewAttached();
-        captureKeyGuardViewManagerCallback();
-
-        // WHEN a touch is received
-        mKeyguardViewManagerCallback.onTouch(
-                MotionEvent.obtain(0, 0, 0, 0, 0, 0));
-
-        // THEN udfpsController onTouch is not called
-        assertTrue(mView.mUseExpandedOverlay);
-        verify(mUdfpsController, never()).onTouch(any());
-    }
-
-    @Test
-    // TODO(b/259264861): Tracking Bug
-    public void testUdfpsExpandedOverlayOff() {
-        // GIVEN view is attached and useExpandedOverlay is false
-        mController.onViewAttached();
-        captureKeyGuardViewManagerCallback();
-
-        // WHEN a touch is received
-        mKeyguardViewManagerCallback.onTouch(
-                MotionEvent.obtain(0, 0, 0, 0, 0, 0));
-
-        // THEN udfpsController onTouch is called
-        assertFalse(mView.mUseExpandedOverlay);
-        verify(mUdfpsController).onTouch(any());
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index da4548b..02ee53879 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -106,10 +106,7 @@
                 mock(SystemClock::class.java),
                 mKeyguardUpdateMonitor,
             )
-        return createUdfpsKeyguardViewController(
-            /* useModernBouncer */ true, /* useExpandedOverlay */
-            false
-        )
+        return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
index 0c8e7a5..9fbe096 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.biometrics
 
-import android.graphics.PointF
-import android.graphics.RectF
 import android.hardware.biometrics.SensorLocationInternal
 import android.testing.TestableLooper
 import android.testing.ViewUtils
@@ -42,7 +40,6 @@
 import org.mockito.Mockito.nullable
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
-import org.mockito.Mockito.`when` as whenever
 
 private const val SENSOR_X = 50
 private const val SENSOR_Y = 250
@@ -80,56 +77,7 @@
         ViewUtils.detachView(view)
     }
 
-    @Test
-    fun layoutSizeFitsSensor() {
-        val params = withArgCaptor<RectF> {
-            verify(animationViewController).onSensorRectUpdated(capture())
-        }
-        assertThat(params.width()).isAtLeast(2f * SENSOR_RADIUS)
-        assertThat(params.height()).isAtLeast(2f * SENSOR_RADIUS)
-    }
-
-    @Test
-    fun isWithinSensorAreaAndPaused() = isWithinSensorArea(paused = true)
-
-    @Test
-    fun isWithinSensorAreaAndNotPaused() = isWithinSensorArea(paused = false)
-
-    private fun isWithinSensorArea(paused: Boolean) {
-        whenever(animationViewController.shouldPauseAuth()).thenReturn(paused)
-        whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f))
-        val end = (SENSOR_RADIUS * 2) - 1
-        for (x in 1 until end) {
-            for (y in 1 until end) {
-                assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isEqualTo(!paused)
-            }
-        }
-    }
-
-    @Test
-    fun isWithinSensorAreaWhenTranslated() {
-        val offset = PointF(100f, 200f)
-        whenever(animationViewController.touchTranslation).thenReturn(offset)
-        val end = (SENSOR_RADIUS * 2) - 1
-        for (x in 0 until offset.x.toInt() step 2) {
-            for (y in 0 until offset.y.toInt() step 2) {
-                assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isFalse()
-            }
-        }
-        for (x in offset.x.toInt() + 1 until offset.x.toInt() + end) {
-            for (y in offset.y.toInt() + 1 until offset.y.toInt() + end) {
-                assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isTrue()
-            }
-        }
-    }
-
-    @Test
-    fun isNotWithinSensorArea() {
-        whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f))
-        assertThat(view.isWithinSensorArea(SENSOR_RADIUS * 2.5f, SENSOR_RADIUS.toFloat()))
-            .isFalse()
-        assertThat(view.isWithinSensorArea(SENSOR_RADIUS.toFloat(), SENSOR_RADIUS * 2.5f)).isFalse()
-    }
+    // TODO: Add test to verify view is size of screen
 
     @Test
     fun startAndStopIllumination() {
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/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
new file mode 100644
index 0000000..30a5497
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
@@ -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.systemui.communal.data.repository
+
+import android.content.pm.UserInfo
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
+    private lateinit var secureSettings: FakeSettings
+    private lateinit var userRepository: FakeUserRepository
+    private lateinit var userTracker: FakeUserTracker
+    private lateinit var logBuffer: LogBuffer
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        logBuffer = FakeLogBuffer.Factory.create()
+        secureSettings = FakeSettings()
+        userRepository = FakeUserRepository()
+        val listOfUserInfo = listOf(MAIN_USER_INFO)
+        userRepository.setUserInfos(listOfUserInfo)
+
+        userTracker = FakeUserTracker()
+        userTracker.set(
+            userInfos = listOfUserInfo,
+            selectedUserIndex = 0,
+        )
+    }
+
+    @Test
+    fun tutorialSettingState_defaultToNotStarted() =
+        testScope.runTest {
+            val repository = initCommunalTutorialRepository()
+            val tutorialSettingState = collectLastValue(repository.tutorialSettingState)()
+            assertThat(tutorialSettingState)
+                .isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED)
+        }
+
+    @Test
+    fun tutorialSettingState_whenTutorialSettingsUpdatedToStarted() =
+        testScope.runTest {
+            val repository = initCommunalTutorialRepository()
+            setTutorialStateSetting(Settings.Secure.HUB_MODE_TUTORIAL_STARTED)
+            val tutorialSettingState = collectLastValue(repository.tutorialSettingState)()
+            assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_STARTED)
+        }
+
+    @Test
+    fun tutorialSettingState_whenTutorialSettingsUpdatedToCompleted() =
+        testScope.runTest {
+            val repository = initCommunalTutorialRepository()
+            setTutorialStateSetting(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+            val tutorialSettingState = collectLastValue(repository.tutorialSettingState)()
+            assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+        }
+
+    private fun initCommunalTutorialRepository(): CommunalTutorialRepositoryImpl {
+        return CommunalTutorialRepositoryImpl(
+            testScope.backgroundScope,
+            testDispatcher,
+            userRepository,
+            secureSettings,
+            userTracker,
+            logBuffer
+        )
+    }
+
+    private fun setTutorialStateSetting(
+        @Settings.Secure.HubModeTutorialState state: Int,
+        user: UserInfo = MAIN_USER_INFO
+    ) {
+        secureSettings.putIntForUser(Settings.Secure.HUB_MODE_TUTORIAL_STATE, state, user.id)
+    }
+
+    companion object {
+        private val MAIN_USER_INFO =
+            UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
new file mode 100644
index 0000000..0a9a15e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.domain.interactor
+
+import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
+import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
+import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_STARTED
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+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.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class CommunalTutorialInteractorTest : SysuiTestCase() {
+
+    @Mock private lateinit var userTracker: UserTracker
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private lateinit var underTest: CommunalTutorialInteractor
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var keyguardInteractor: KeyguardInteractor
+    private lateinit var communalTutorialRepository: FakeCommunalTutorialRepository
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        val withDeps = KeyguardInteractorFactory.create()
+        keyguardInteractor = withDeps.keyguardInteractor
+        keyguardRepository = withDeps.repository
+        communalTutorialRepository = FakeCommunalTutorialRepository()
+
+        underTest =
+            CommunalTutorialInteractor(
+                keyguardInteractor = keyguardInteractor,
+                communalTutorialRepository = communalTutorialRepository,
+            )
+
+        whenever(userTracker.userHandle).thenReturn(mock())
+    }
+
+    @Test
+    fun tutorialUnavailable_whenKeyguardNotVisible() =
+        testScope.runTest {
+            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
+            keyguardRepository.setKeyguardShowing(false)
+            assertThat(isTutorialAvailable).isFalse()
+        }
+
+    @Test
+    fun tutorialUnavailable_whenTutorialIsCompleted() =
+        testScope.runTest {
+            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+            assertThat(isTutorialAvailable).isFalse()
+        }
+
+    @Test
+    fun tutorialAvailable_whenTutorialNotStarted() =
+        testScope.runTest {
+            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
+            assertThat(isTutorialAvailable).isTrue()
+        }
+
+    @Test
+    fun tutorialAvailable_whenTutorialIsStarted() =
+        testScope.runTest {
+            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
+            assertThat(isTutorialAvailable).isTrue()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt
index 41a8be9..33a6667 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt
@@ -6,6 +6,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalHubSection
 import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalWidgetSection
 import org.junit.Before
 import org.junit.Test
@@ -18,6 +19,7 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class DefaultCommunalBlueprintTest : SysuiTestCase() {
+    @Mock private lateinit var hubSection: DefaultCommunalHubSection
     @Mock private lateinit var widgetSection: DefaultCommunalWidgetSection
 
     private lateinit var blueprint: DefaultCommunalBlueprint
@@ -25,13 +27,14 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        blueprint = DefaultCommunalBlueprint(widgetSection)
+        blueprint = DefaultCommunalBlueprint(hubSection, widgetSection)
     }
 
     @Test
     fun addView() {
         val constraintLayout = ConstraintLayout(context, null)
         blueprint.replaceViews(null, constraintLayout)
+        verify(hubSection).addViews(constraintLayout)
         verify(widgetSection).addViews(constraintLayout)
     }
 
@@ -39,6 +42,7 @@
     fun applyConstraints() {
         val cs = ConstraintSet()
         blueprint.applyConstraints(cs)
+        verify(hubSection).applyConstraints(cs)
         verify(widgetSection).applyConstraints(cs)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
index 68ea7eb..6c2e136 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.demomode.DemoMode.ACTION_DEMO
 import com.android.systemui.demomode.DemoMode.COMMAND_STATUS
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.FakeGlobalSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
@@ -48,7 +48,7 @@
 
     @Mock private lateinit var dumpManager: DumpManager
 
-    private val globalSettings = FakeSettings()
+    private val globalSettings = FakeGlobalSettings()
 
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
index 679dfef..2c80035 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -12,6 +12,7 @@
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.argumentCaptor
 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.runBlocking
@@ -22,6 +23,7 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -54,6 +56,7 @@
                 keyguardBypassController = keyguardBypassController,
                 keyguardStateController = keyguardStateController,
             )
+        testScope.runCurrent()
     }
 
     @Test
@@ -66,8 +69,7 @@
             assertThat(isUnlocked).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
-            Mockito.verify(keyguardStateController, Mockito.atLeastOnce())
-                .addCallback(captor.capture())
+            verify(keyguardStateController, Mockito.atLeastOnce()).addCallback(captor.capture())
 
             whenever(keyguardStateController.isUnlocked).thenReturn(true)
             captor.value.onUnlockedChanged()
@@ -98,7 +100,12 @@
         testScope.runTest {
             whenever(keyguardBypassController.isBypassEnabled).thenAnswer { false }
             whenever(keyguardBypassController.bypassEnabled).thenAnswer { false }
-            assertThat(underTest.isBypassEnabled()).isFalse()
+            withArgCaptor {
+                    verify(keyguardBypassController).registerOnBypassStateChangedListener(capture())
+                }
+                .onBypassStateChanged(false)
+            runCurrent()
+            assertThat(underTest.isBypassEnabled.value).isFalse()
         }
 
     @Test
@@ -106,7 +113,12 @@
         testScope.runTest {
             whenever(keyguardBypassController.isBypassEnabled).thenAnswer { true }
             whenever(keyguardBypassController.bypassEnabled).thenAnswer { true }
-            assertThat(underTest.isBypassEnabled()).isTrue()
+            withArgCaptor {
+                    verify(keyguardBypassController).registerOnBypassStateChangedListener(capture())
+                }
+                .onBypassStateChanged(true)
+            runCurrent()
+            assertThat(underTest.isBypassEnabled.value).isTrue()
         }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index c4cd8a4..c13fde7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -218,14 +218,14 @@
     fun isBypassEnabled_enabledInRepository_true() =
         testScope.runTest {
             utils.deviceEntryRepository.setBypassEnabled(true)
-            assertThat(underTest.isBypassEnabled()).isTrue()
+            assertThat(underTest.isBypassEnabled.value).isTrue()
         }
 
     @Test
     fun isBypassEnabled_disabledInRepository_false() =
         testScope.runTest {
             utils.deviceEntryRepository.setBypassEnabled(false)
-            assertThat(underTest.isBypassEnabled()).isFalse()
+            assertThat(underTest.isBypassEnabled.value).isFalse()
         }
 
     private fun switchToScene(sceneKey: SceneKey) {
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 14c5ec0..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
@@ -36,7 +37,6 @@
 import org.junit.Assert
 import org.junit.Before
 import org.junit.Test
-import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.anyString
@@ -66,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)
@@ -73,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 =
@@ -85,6 +86,7 @@
                 resources,
                 serverFlagReader,
                 flagMap,
+                fakeGantryFlags,
                 restarter
             )
         mFeatureFlagsClassicDebug.init()
@@ -122,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()
 
@@ -134,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()
 
@@ -150,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()
 
@@ -479,7 +477,7 @@
                 verify(flagManager, times(numReads))
                     .readFlagValue(eq(name), any<FlagSerializer<*>>())
                 verify(flagManager).nameToSettingsKey(eq(name))
-                verify(globalSettings).putStringForUser(eq("key-$name"), eq(data), anyInt())
+                verify(globalSettings).putString(eq("key-$name"), eq(data))
                 verify(flagManager).dispatchListenersAndMaybeRestart(eq(name), any())
             }
             .verifyNoMoreInteractions()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
new file mode 100644
index 0000000..bb6786a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.flags
+
+import android.platform.test.flag.junit.SetFlagsRule
+
+fun SetFlagsRule.setFlagDefault(flagName: String) {
+    if (getFlagDefault(flagName)) {
+        enableFlags(flagName)
+    } else {
+        disableFlags(flagName)
+    }
+}
+
+// NOTE: This code uses reflection to gain access to private members of aconfig generated
+//  classes (in the same way SetFlagsRule does internally) because this is the only way to get
+//  at the underlying information and read the current value of the flag.
+// If aconfig had flag constants with accessible default values, this would be unnecessary.
+private fun getFlagDefault(name: String): Boolean {
+    val flagPackage = name.substringBeforeLast(".")
+    val featureFlagsImplClass = Class.forName("$flagPackage.FeatureFlagsImpl")
+    val featureFlagsImpl = featureFlagsImplClass.getConstructor().newInstance()
+    val flagMethodName = name.substringAfterLast(".").snakeToCamelCase()
+    val flagGetter = featureFlagsImplClass.getDeclaredMethod(flagMethodName)
+    return flagGetter.invoke(featureFlagsImpl) as Boolean
+}
+
+private fun String.snakeToCamelCase(): String {
+    val pattern = "_[a-z]".toRegex()
+    return replace(pattern) { it.value.last().uppercase() }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index b1cf051..2d3f69d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -78,6 +78,8 @@
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.RingerModeLiveData;
 import com.android.systemui.util.RingerModeTracker;
+import com.android.systemui.util.settings.FakeGlobalSettings;
+import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -105,8 +107,8 @@
     @Mock private LockPatternUtils mLockPatternUtils;
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private TelephonyListenerManager mTelephonyListenerManager;
-    @Mock private GlobalSettings mGlobalSettings;
-    @Mock private SecureSettings mSecureSettings;
+    private GlobalSettings mGlobalSettings;
+    private SecureSettings mSecureSettings;
     @Mock private Resources mResources;
     @Mock private ConfigurationController mConfigurationController;
     @Mock private UserTracker mUserTracker;
@@ -148,6 +150,9 @@
         when(mResources.getConfiguration()).thenReturn(
                 getContext().getResources().getConfiguration());
 
+        mGlobalSettings = new FakeGlobalSettings();
+        mSecureSettings = new FakeSettings();
+
         mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext,
                 mWindowManagerFuncs,
                 mAudioManager,
@@ -592,8 +597,8 @@
         UserInfo currentUser = mockCurrentUser(FLAG_ADMIN);
 
         when(mGlobalActionsDialogLite.getCurrentUser()).thenReturn(currentUser);
-        when(mGlobalSettings.getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU,
-                0, currentUser.id)).thenReturn(1);
+        mSecureSettings.putIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 1,
+                currentUser.id);
 
         GlobalActionsDialogLite.BugReportAction bugReportAction =
                 mGlobalActionsDialogLite.makeBugReportActionForTesting();
@@ -605,8 +610,8 @@
         UserInfo currentUser = mockCurrentUser(0);
 
         when(mGlobalActionsDialogLite.getCurrentUser()).thenReturn(currentUser);
-        doReturn(1).when(mGlobalSettings)
-                .getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, currentUser.id);
+        mSecureSettings.putIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 1,
+                currentUser.id);
 
         GlobalActionsDialogLite.BugReportAction bugReportAction =
                 mGlobalActionsDialogLite.makeBugReportActionForTesting();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
index 632d149..f373062 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
@@ -16,23 +16,17 @@
 
 package com.android.systemui.keyevent.domain.interactor
 
-import android.view.KeyEvent
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.back.domain.interactor.BackActionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyevent.data.repository.FakeKeyEventRepository
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
 
 @SmallTest
@@ -40,108 +34,27 @@
 class KeyEventInteractorTest : SysuiTestCase() {
     @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
 
-    private lateinit var keyguardInteractorWithDependencies:
-        KeyguardInteractorFactory.WithDependencies
-    @Mock private lateinit var keyguardKeyEventInteractor: KeyguardKeyEventInteractor
-    @Mock private lateinit var backActionInteractor: BackActionInteractor
+    private lateinit var repository: FakeKeyEventRepository
 
     private lateinit var underTest: KeyEventInteractor
 
     @Before
     fun setup() {
-        keyguardInteractorWithDependencies = KeyguardInteractorFactory.create()
+        repository = FakeKeyEventRepository()
         underTest =
             KeyEventInteractor(
-                backActionInteractor,
-                keyguardKeyEventInteractor,
+                repository,
             )
     }
 
     @Test
-    fun dispatchBackKey_notHandledByKeyguardKeyEventInteractor_handledByBackActionInteractor() {
-        val backKeyEventActionDown = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK)
-        val backKeyEventActionUp = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)
+    fun dispatchBackKey_notHandledByKeyguardKeyEventInteractor_handledByBackActionInteractor() =
+        runTest {
+            val isPowerDown by collectLastValue(underTest.isPowerButtonDown)
+            repository.setPowerButtonDown(false)
+            assertThat(isPowerDown).isFalse()
 
-        // GIVEN back key ACTION_DOWN and ACTION_UP aren't handled by the keyguardKeyEventInteractor
-        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionDown))
-            .thenReturn(false)
-        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionUp))
-            .thenReturn(false)
-
-        // WHEN back key event ACTION_DOWN, the event is handled even though back isn't requested
-        assertThat(underTest.dispatchKeyEvent(backKeyEventActionDown)).isTrue()
-        // THEN back event isn't handled on ACTION_DOWN
-        verify(backActionInteractor, never()).onBackRequested()
-
-        // WHEN back key event ACTION_UP
-        assertThat(underTest.dispatchKeyEvent(backKeyEventActionUp)).isTrue()
-        // THEN back event is handled on ACTION_UP
-        verify(backActionInteractor).onBackRequested()
-    }
-
-    @Test
-    fun dispatchKeyEvent_isNotHandledByKeyguardKeyEventInteractor() {
-        val keyEvent =
-            KeyEvent(
-                KeyEvent.ACTION_UP,
-                KeyEvent.KEYCODE_SPACE,
-            )
-        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(false)
-        assertThat(underTest.dispatchKeyEvent(keyEvent)).isFalse()
-    }
-
-    @Test
-    fun dispatchKeyEvent_handledByKeyguardKeyEventInteractor() {
-        val keyEvent =
-            KeyEvent(
-                KeyEvent.ACTION_UP,
-                KeyEvent.KEYCODE_SPACE,
-            )
-        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(true)
-        assertThat(underTest.dispatchKeyEvent(keyEvent)).isTrue()
-    }
-
-    @Test
-    fun interceptMediaKey_isNotHandledByKeyguardKeyEventInteractor() {
-        val keyEvent =
-            KeyEvent(
-                KeyEvent.ACTION_UP,
-                KeyEvent.KEYCODE_SPACE,
-            )
-        whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(false)
-        assertThat(underTest.interceptMediaKey(keyEvent)).isFalse()
-    }
-
-    @Test
-    fun interceptMediaKey_handledByKeyguardKeyEventInteractor() {
-        val keyEvent =
-            KeyEvent(
-                KeyEvent.ACTION_UP,
-                KeyEvent.KEYCODE_SPACE,
-            )
-        whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(true)
-        assertThat(underTest.interceptMediaKey(keyEvent)).isTrue()
-    }
-
-    @Test
-    fun dispatchKeyEventPreIme_isNotHandledByKeyguardKeyEventInteractor() {
-        val keyEvent =
-            KeyEvent(
-                KeyEvent.ACTION_UP,
-                KeyEvent.KEYCODE_SPACE,
-            )
-        whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(false)
-        assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isFalse()
-    }
-
-    @Test
-    fun dispatchKeyEventPreIme_handledByKeyguardKeyEventInteractor() {
-        val keyEvent =
-            KeyEvent(
-                KeyEvent.ACTION_UP,
-                KeyEvent.KEYCODE_SPACE,
-            )
-        whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(true)
-        assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isTrue()
-    }
+            repository.setPowerButtonDown(true)
+            assertThat(isPowerDown).isTrue()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerTest.kt
new file mode 100644
index 0000000..af00a48
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerTest.kt
@@ -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.systemui.keyevent.domain.interactor
+
+import android.view.KeyEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SysUIKeyEventHandlerTest : SysuiTestCase() {
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+    private lateinit var keyguardInteractorWithDependencies:
+        KeyguardInteractorFactory.WithDependencies
+    @Mock private lateinit var keyguardKeyEventInteractor: KeyguardKeyEventInteractor
+    @Mock private lateinit var backActionInteractor: BackActionInteractor
+
+    private lateinit var underTest: SysUIKeyEventHandler
+
+    @Before
+    fun setup() {
+        keyguardInteractorWithDependencies = KeyguardInteractorFactory.create()
+        underTest =
+            SysUIKeyEventHandler(
+                backActionInteractor,
+                keyguardKeyEventInteractor,
+            )
+    }
+
+    @Test
+    fun dispatchBackKey_notHandledByKeyguardKeyEventInteractor_handledByBackActionInteractor() {
+        val backKeyEventActionDown = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK)
+        val backKeyEventActionUp = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)
+
+        // GIVEN back key ACTION_DOWN and ACTION_UP aren't handled by the keyguardKeyEventInteractor
+        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionDown))
+            .thenReturn(false)
+        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionUp))
+            .thenReturn(false)
+
+        // WHEN back key event ACTION_DOWN, the event is handled even though back isn't requested
+        assertThat(underTest.dispatchKeyEvent(backKeyEventActionDown)).isTrue()
+        // THEN back event isn't handled on ACTION_DOWN
+        verify(backActionInteractor, never()).onBackRequested()
+
+        // WHEN back key event ACTION_UP
+        assertThat(underTest.dispatchKeyEvent(backKeyEventActionUp)).isTrue()
+        // THEN back event is handled on ACTION_UP
+        verify(backActionInteractor).onBackRequested()
+    }
+
+    @Test
+    fun dispatchKeyEvent_isNotHandledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(false)
+        assertThat(underTest.dispatchKeyEvent(keyEvent)).isFalse()
+    }
+
+    @Test
+    fun dispatchKeyEvent_handledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(true)
+        assertThat(underTest.dispatchKeyEvent(keyEvent)).isTrue()
+    }
+
+    @Test
+    fun interceptMediaKey_isNotHandledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(false)
+        assertThat(underTest.interceptMediaKey(keyEvent)).isFalse()
+    }
+
+    @Test
+    fun interceptMediaKey_handledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(true)
+        assertThat(underTest.interceptMediaKey(keyEvent)).isTrue()
+    }
+
+    @Test
+    fun dispatchKeyEventPreIme_isNotHandledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(false)
+        assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isFalse()
+    }
+
+    @Test
+    fun dispatchKeyEventPreIme_handledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(true)
+        assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
index faf9751..977f1db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
@@ -25,13 +25,13 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.notification.EnableZenModeDialog
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Expandable
 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.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.ZenModeController
 import com.android.systemui.util.mockito.argumentCaptor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
index 1e80fb6..26fcb23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -22,8 +22,8 @@
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.util.FakeSharedPreferences
@@ -70,8 +70,7 @@
         val resources: Resources = mock()
         whenever(resources.getStringArray(R.array.config_keyguardQuickAffordanceDefaults))
             .thenReturn(emptyArray())
-        whenever(resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled))
-            .thenReturn(true)
+        whenever(resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled)).thenReturn(true)
         whenever(context.resources).thenReturn(resources)
 
         testDispatcher = UnconfinedTestDispatcher()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 9ee22c8..b32905f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -204,8 +204,7 @@
                     fromScene = SceneKey.Gone,
                     toScene = SceneKey.Lockscreen,
                     progress = flowOf(0f),
-                    isInitiatedByUserInput = false,
-                    isUserInputOngoing = flowOf(false),
+                    isUserInputDriven = false,
                 )
             runCurrent()
             assertThat(isAnimate).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
index bbe6823..a5d7457 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
@@ -57,8 +57,6 @@
         KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP)
     private val backKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)
 
-    private lateinit var keyguardInteractorWithDependencies:
-        KeyguardInteractorFactory.WithDependencies
     private lateinit var powerInteractor: PowerInteractor
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@@ -73,14 +71,12 @@
     fun setup() {
         whenever(mediaSessionLegacyHelperWrapper.getHelper(any()))
             .thenReturn(mediaSessionLegacyHelper)
-        keyguardInteractorWithDependencies = KeyguardInteractorFactory.create()
         powerInteractor = PowerInteractorFactory.create().powerInteractor
 
         underTest =
             KeyguardKeyEventInteractor(
                 context,
                 statusBarStateController,
-                keyguardInteractorWithDependencies.keyguardInteractor,
                 statusBarKeyguardViewManager,
                 shadeController,
                 mediaSessionLegacyHelperWrapper,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 2cf0e77..5d5ece0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -433,7 +433,9 @@
 
             // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
             runTransitionAndSetWakefulness(
-                    KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                KeyguardState.GONE,
+                KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+            )
 
             // WHEN the lockscreen hosted dream stops
             keyguardRepository.setIsActiveDreamLockscreenHosted(false)
@@ -457,7 +459,9 @@
         testScope.runTest {
             // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
             runTransitionAndSetWakefulness(
-                    KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                KeyguardState.GONE,
+                KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+            )
 
             // WHEN biometrics succeeds with wake and unlock from dream mode
             keyguardRepository.setBiometricUnlockState(
@@ -487,7 +491,9 @@
 
             // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
             runTransitionAndSetWakefulness(
-                    KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                KeyguardState.GONE,
+                KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+            )
 
             // WHEN the primary bouncer is set to show
             bouncerRepository.setPrimaryShow(true)
@@ -515,7 +521,9 @@
 
             // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
             runTransitionAndSetWakefulness(
-                    KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                KeyguardState.GONE,
+                KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+            )
 
             // WHEN the device begins to sleep
             keyguardRepository.setIsActiveDreamLockscreenHosted(false)
@@ -547,7 +555,9 @@
 
             // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
             runTransitionAndSetWakefulness(
-                    KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                KeyguardState.GONE,
+                KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+            )
 
             // WHEN the keyguard is occluded and the lockscreen hosted dream stops
             keyguardRepository.setIsActiveDreamLockscreenHosted(false)
@@ -783,7 +793,9 @@
         testScope.runTest {
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
             runTransitionAndSetWakefulness(
-                    KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER)
+                KeyguardState.LOCKSCREEN,
+                KeyguardState.ALTERNATE_BOUNCER
+            )
 
             // WHEN the alternateBouncer stops showing and then the primary bouncer shows
             bouncerRepository.setPrimaryShow(true)
@@ -808,7 +820,9 @@
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
             bouncerRepository.setAlternateVisible(true)
             runTransitionAndSetWakefulness(
-                    KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER)
+                KeyguardState.LOCKSCREEN,
+                KeyguardState.ALTERNATE_BOUNCER
+            )
 
             // GIVEN the primary bouncer isn't showing, aod available and starting to sleep
             bouncerRepository.setPrimaryShow(false)
@@ -838,7 +852,9 @@
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
             bouncerRepository.setAlternateVisible(true)
             runTransitionAndSetWakefulness(
-                    KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER)
+                KeyguardState.LOCKSCREEN,
+                KeyguardState.ALTERNATE_BOUNCER
+            )
 
             // GIVEN the primary bouncer isn't showing, aod not available and starting to sleep
             // to sleep
@@ -869,7 +885,9 @@
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
             bouncerRepository.setAlternateVisible(true)
             runTransitionAndSetWakefulness(
-                    KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER)
+                KeyguardState.LOCKSCREEN,
+                KeyguardState.ALTERNATE_BOUNCER
+            )
 
             // GIVEN the primary bouncer isn't showing and device not sleeping
             bouncerRepository.setPrimaryShow(false)
@@ -980,7 +998,9 @@
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
             bouncerRepository.setPrimaryShow(true)
             runTransitionAndSetWakefulness(
-                    KeyguardState.DREAMING_LOCKSCREEN_HOSTED, KeyguardState.PRIMARY_BOUNCER)
+                KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                KeyguardState.PRIMARY_BOUNCER
+            )
 
             // WHEN the primary bouncer stops showing and lockscreen hosted dream still active
             bouncerRepository.setPrimaryShow(false)
@@ -1161,6 +1181,57 @@
         }
 
     @Test
+    fun dreamingToOccluded() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to DREAMING
+            keyguardRepository.setDreaming(true)
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
+            runCurrent()
+
+            // WHEN the keyguard is occluded and device wakes up and is no longer dreaming
+            keyguardRepository.setDreaming(false)
+            keyguardRepository.setKeyguardOccluded(true)
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                }
+            // THEN a transition to OCCLUDED should occur
+            assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
+            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    fun lockscreenToOccluded() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to LOCKSCREEN
+            runTransitionAndSetWakefulness(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
+            runCurrent()
+
+            // WHEN the keyguard is occluded
+            keyguardRepository.setKeyguardOccluded(true)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                }
+            // THEN a transition to OCCLUDED should occur
+            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
+            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun aodToOccluded() =
         testScope.runTest {
             // GIVEN a prior transition has run to AOD
@@ -1286,8 +1357,8 @@
     }
 
     private suspend fun TestScope.runTransitionAndSetWakefulness(
-            from: KeyguardState,
-            to: KeyguardState
+        from: KeyguardState,
+        to: KeyguardState
     ) {
         transitionRepository.sendTransitionStep(
             TransitionStep(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 7940b45..50ee026 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -23,6 +23,7 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
@@ -65,6 +66,7 @@
     @Mock private lateinit var splitShadeGuidelines: SplitShadeGuidelines
     @Mock private lateinit var aodNotificationIconsSection: AodNotificationIconsSection
     @Mock private lateinit var aodBurnInSection: AodBurnInSection
+    @Mock private lateinit var communalTutorialIndicatorSection: CommunalTutorialIndicatorSection
 
     @Before
     fun setup() {
@@ -83,6 +85,7 @@
                 splitShadeGuidelines,
                 aodNotificationIconsSection,
                 aodBurnInSection,
+                communalTutorialIndicatorSection,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
new file mode 100644
index 0000000..255f4df
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.collect.Range
+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.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GoneToAodTransitionViewModelTest : SysuiTestCase() {
+    private lateinit var underTest: GoneToAodTransitionViewModel
+    private lateinit var repository: FakeKeyguardTransitionRepository
+    private lateinit var testScope: TestScope
+
+    @Before
+    fun setUp() {
+        val testDispatcher = StandardTestDispatcher()
+        testScope = TestScope(testDispatcher)
+
+        repository = FakeKeyguardTransitionRepository()
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
+        underTest = GoneToAodTransitionViewModel(interactor)
+    }
+
+    @Test
+    fun enterFromTopTranslationY() =
+        testScope.runTest {
+            val pixels = -100f
+            val enterFromTopTranslationY by
+                collectLastValue(underTest.enterFromTopTranslationY(pixels.toInt()))
+
+            // The animation should only start > halfway through
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            assertThat(enterFromTopTranslationY).isEqualTo(pixels)
+
+            repository.sendTransitionStep(step(0.5f))
+            assertThat(enterFromTopTranslationY).isEqualTo(pixels)
+
+            repository.sendTransitionStep(step(.85f))
+            assertThat(enterFromTopTranslationY).isIn(Range.closed(pixels, 0f))
+
+            // At the end, the translation should be complete and set to zero
+            repository.sendTransitionStep(step(1f))
+            assertThat(enterFromTopTranslationY).isEqualTo(0f)
+        }
+
+    @Test
+    fun enterFromTopAnimationAlpha() =
+        testScope.runTest {
+            val enterFromTopAnimationAlpha by collectLastValue(underTest.enterFromTopAnimationAlpha)
+
+            // The animation should only start > halfway through
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            assertThat(enterFromTopAnimationAlpha).isEqualTo(0f)
+
+            repository.sendTransitionStep(step(0.5f))
+            assertThat(enterFromTopAnimationAlpha).isEqualTo(0f)
+
+            repository.sendTransitionStep(step(.85f))
+            assertThat(enterFromTopAnimationAlpha).isIn(Range.closed(0f, 1f))
+
+            repository.sendTransitionStep(step(1f))
+            assertThat(enterFromTopAnimationAlpha).isEqualTo(1f)
+        }
+
+    private fun step(
+        value: Float,
+        state: TransitionState = TransitionState.RUNNING
+    ): TransitionStep {
+        return TransitionStep(
+            from = KeyguardState.GONE,
+            to = KeyguardState.AOD,
+            value = value,
+            transitionState = state,
+            ownerName = "GoneToAodTransitionViewModelTest"
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 71688db..4f545cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -17,8 +17,10 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -26,12 +28,17 @@
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import javax.inject.Provider
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
@@ -41,6 +48,7 @@
 import org.junit.runners.JUnit4
 import org.mockito.Answers
 import org.mockito.Mock
+import org.mockito.Mockito.anyInt
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -51,10 +59,20 @@
     private lateinit var testScope: TestScope
     private lateinit var repository: FakeKeyguardRepository
     private lateinit var keyguardInteractor: KeyguardInteractor
+    private lateinit var configurationRepository: FakeConfigurationRepository
     @Mock private lateinit var burnInInteractor: BurnInInteractor
+    @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+    @Mock private lateinit var goneToAodTransitionViewModel: GoneToAodTransitionViewModel
+    @Mock
+    private lateinit var aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel
     @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController
 
     private val burnInFlow = MutableStateFlow(BurnInModel())
+    private val goneToAodTransitionViewModelVisibility = MutableStateFlow(0)
+    private val enterFromTopAnimationAlpha = MutableStateFlow(0f)
+    private val goneToAodTransitionStep = MutableSharedFlow<TransitionStep>(replay = 1)
+    private val dozeAmountTransitionStep = MutableSharedFlow<TransitionStep>(replay = 1)
+    private val startedKeyguardState = MutableStateFlow(KeyguardState.GONE)
 
     @Before
     fun setUp() {
@@ -71,9 +89,30 @@
         val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
         keyguardInteractor = withDeps.keyguardInteractor
         repository = withDeps.repository
+        configurationRepository = withDeps.configurationRepository
+
+        whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt()))
+            .thenReturn(emptyFlow<Float>())
+        whenever(goneToAodTransitionViewModel.enterFromTopAnimationAlpha)
+            .thenReturn(enterFromTopAnimationAlpha)
 
         whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow)
-        underTest = KeyguardRootViewModel(keyguardInteractor, burnInInteractor)
+
+        whenever(keyguardTransitionInteractor.goneToAodTransition)
+            .thenReturn(goneToAodTransitionStep)
+        whenever(keyguardTransitionInteractor.dozeAmountTransition)
+            .thenReturn(dozeAmountTransitionStep)
+        whenever(keyguardTransitionInteractor.startedKeyguardState).thenReturn(startedKeyguardState)
+
+        underTest =
+            KeyguardRootViewModel(
+                context,
+                keyguardInteractor,
+                burnInInteractor,
+                goneToAodTransitionViewModel,
+                aodToLockscreenTransitionViewModel,
+                keyguardTransitionInteractor,
+            )
         underTest.clockControllerProvider = Provider { clockController }
     }
 
@@ -118,7 +157,7 @@
             val scale by collectLastValue(underTest.scale)
 
             // Set to not dozing (on lockscreen)
-            repository.setDozeAmount(0f)
+            dozeAmountTransitionStep.emit(TransitionStep(value = 0f))
 
             // Trigger a change to the burn-in model
             burnInFlow.value =
@@ -141,8 +180,7 @@
             val scale by collectLastValue(underTest.scale)
 
             // Set to dozing (on AOD)
-            repository.setDozeAmount(1f)
-
+            dozeAmountTransitionStep.emit(TransitionStep(value = 1f))
             // Trigger a change to the burn-in model
             burnInFlow.value =
                 BurnInModel(
@@ -150,10 +188,15 @@
                     translationY = 30,
                     scale = 0.5f,
                 )
-
             assertThat(translationX).isEqualTo(20)
             assertThat(translationY).isEqualTo(30)
             assertThat(scale).isEqualTo(Pair(0.5f, true /* scaleClockOnly */))
+
+            // Set to the beginning of GONE->AOD transition
+            goneToAodTransitionStep.emit(TransitionStep(value = 0f))
+            assertThat(translationX).isEqualTo(0)
+            assertThat(translationY).isEqualTo(0)
+            assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */))
         }
 
     @Test
@@ -166,7 +209,7 @@
             val scale by collectLastValue(underTest.scale)
 
             // Set to dozing (on AOD)
-            repository.setDozeAmount(1f)
+            dozeAmountTransitionStep.emit(TransitionStep(value = 1f))
 
             // Trigger a change to the burn-in model
             burnInFlow.value =
@@ -180,4 +223,28 @@
             assertThat(translationY).isEqualTo(0)
             assertThat(scale).isEqualTo(Pair(0.5f, false /* scaleClockOnly */))
         }
+
+    @Test
+    fun burnInLayerVisibility() =
+        testScope.runTest {
+            val burnInLayerVisibility by collectLastValue(underTest.burnInLayerVisibility)
+
+            startedKeyguardState.value = KeyguardState.OCCLUDED
+            assertThat(burnInLayerVisibility).isNull()
+
+            startedKeyguardState.value = KeyguardState.AOD
+            assertThat(burnInLayerVisibility).isEqualTo(View.VISIBLE)
+        }
+
+    @Test
+    fun burnInLayerAlpha() =
+        testScope.runTest {
+            val burnInLayerAlpha by collectLastValue(underTest.burnInLayerAlpha)
+
+            enterFromTopAnimationAlpha.value = 0.2f
+            assertThat(burnInLayerAlpha).isEqualTo(0.2f)
+
+            enterFromTopAnimationAlpha.value = 1f
+            assertThat(burnInLayerAlpha).isEqualTo(1f)
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 9364097..d7802aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -60,10 +60,7 @@
         MockitoAnnotations.initMocks(this)
         repository = FakeKeyguardTransitionRepository()
         val featureFlags =
-            FakeFeatureFlags().apply {
-                set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
-                set(Flags.UDFPS_NEW_TOUCH_DETECTION, true)
-            }
+            FakeFeatureFlags().apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) }
         val interactor =
             KeyguardTransitionInteractorFactory.create(
                     scope = TestScope().backgroundScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index fb0a4be..59d8104 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -48,7 +48,6 @@
 import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.InstanceIdSequenceFake
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dump.DumpManager
@@ -63,6 +62,7 @@
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.SbnBuilder
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -2204,6 +2204,85 @@
     }
 
     @Test
+    fun testSessionPlayer_sessionDestroyed_noResume_fullyRemoved() {
+        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        addPlaybackStateAction()
+
+        // When a media control with PlaybackState actions is added, times out,
+        // and then the session is destroyed
+        addNotificationAndLoad()
+        val data = mediaDataCaptor.value
+        assertThat(data.active).isTrue()
+        mediaDataManager.setTimedOut(KEY, timedOut = true)
+        sessionCallbackCaptor.value.invoke(KEY)
+
+        // It is fully removed.
+        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
+        verify(listener, never())
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+    }
+
+    @Test
+    fun testSessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
+        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        addPlaybackStateAction()
+
+        // When a media control using session actions is added, and then the session is destroyed
+        // without timing out first
+        addNotificationAndLoad()
+        val data = mediaDataCaptor.value
+        assertThat(data.active).isTrue()
+        sessionCallbackCaptor.value.invoke(KEY)
+
+        // It is fully removed
+        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
+        verify(listener, never())
+            .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
+    }
+
+    @Test
+    fun testSessionPlayer_canResume_destroyedWhileActive_setToResume() {
+        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        addPlaybackStateAction()
+
+        // When a media control using session actions and that does allow resumption is added,
+        addNotificationAndLoad()
+        val dataResumable = mediaDataCaptor.value.copy(resumeAction = Runnable {})
+        mediaDataManager.onMediaDataLoaded(KEY, null, dataResumable)
+
+        // And then the session is destroyed without timing out first
+        sessionCallbackCaptor.value.invoke(KEY)
+
+        // It is converted to a resume player
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+        assertThat(mediaDataCaptor.value.resumption).isTrue()
+        assertThat(mediaDataCaptor.value.active).isFalse()
+        verify(logger)
+            .logActiveConvertedToResume(
+                anyInt(),
+                eq(PACKAGE_NAME),
+                eq(mediaDataCaptor.value.instanceId)
+            )
+    }
+
+    @Test
     fun testSessionDestroyed_noNotificationKey_stillRemoved() {
         whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
         whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
index 85d3fba..deefab6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
@@ -21,6 +21,7 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.graphics.drawable.Drawable
+import android.media.MediaRoute2Info
 import android.media.MediaRouter2Manager
 import android.media.RoutingSessionInfo
 import android.media.session.MediaController
@@ -34,15 +35,18 @@
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
 import com.android.settingslib.media.LocalMediaManager
 import com.android.settingslib.media.MediaDevice
-import com.android.systemui.res.R
+import com.android.settingslib.media.PhoneMediaDevice
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.media.controls.MediaTestUtils
 import com.android.systemui.media.controls.models.player.MediaData
 import com.android.systemui.media.controls.models.player.MediaDeviceData
 import com.android.systemui.media.controls.util.MediaControllerFactory
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.eq
@@ -95,6 +99,7 @@
     @Mock private lateinit var device: MediaDevice
     @Mock private lateinit var icon: Drawable
     @Mock private lateinit var route: RoutingSessionInfo
+    @Mock private lateinit var selectedRoute: MediaRoute2Info
     @Mock private lateinit var controller: MediaController
     @Mock private lateinit var playbackInfo: PlaybackInfo
     @Mock private lateinit var configurationController: ConfigurationController
@@ -107,6 +112,7 @@
     private lateinit var session: MediaSession
     private lateinit var mediaData: MediaData
     @JvmField @Rule val mockito = MockitoJUnit.rule()
+    private val featureFlags = FakeFeatureFlagsClassic()
 
     @Before
     fun setUp() {
@@ -124,7 +130,8 @@
                 localBluetoothManager,
                 fakeFgExecutor,
                 fakeBgExecutor,
-                dumpster
+                dumpster,
+                featureFlags,
             )
         manager.addListener(listener)
 
@@ -143,6 +150,7 @@
             MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, token = session.sessionToken)
         whenever(controllerFactory.create(session.sessionToken)).thenReturn(controller)
         setupLeAudioConfiguration(false)
+        featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, false)
     }
 
     @After
@@ -454,9 +462,54 @@
     }
 
     @Test
-    fun mr2ReturnsRouteWithNullName_useLocalDeviceName() {
+    fun mr2ReturnsSystemRouteWithNullName_isPhone_usePhoneName() {
+        featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
+        // When the routing session name is null, and is a system session for a PhoneMediaDevice
+        val phoneDevice = mock(PhoneMediaDevice::class.java)
+        whenever(phoneDevice.iconWithoutBackground).thenReturn(icon)
+        whenever(lmm.currentConnectedDevice).thenReturn(phoneDevice)
+        whenever(route.isSystemSession).thenReturn(true)
+
+        whenever(route.name).thenReturn(null)
+        whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute))
+        whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME)
+        whenever(selectedRoute.type).thenReturn(MediaRoute2Info.TYPE_BUILTIN_SPEAKER)
+
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+
+        // Then the device name is the PhoneMediaDevice string
+        val data = captureDeviceData(KEY)
+        assertThat(data.name)
+            .isEqualTo(
+                context.getString(com.android.settingslib.R.string.media_transfer_this_device_name)
+            )
+    }
+
+    @Test
+    fun mr2ReturnsSystemRouteWithNullName_useSelectedRouteName() {
+        featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
+        // When the routing session does not have a name, and is a system session
+        whenever(route.name).thenReturn(null)
+        whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute))
+        whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME)
+        whenever(route.isSystemSession).thenReturn(true)
+
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+
+        // Then the device name is the selected route name
+        val data = captureDeviceData(KEY)
+        assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME)
+    }
+
+    @Test
+    fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName() {
         // GIVEN that MR2Manager returns a routing session that does not have a name
         whenever(route.name).thenReturn(null)
+        whenever(route.isSystemSession).thenReturn(false)
         // WHEN a notification is added
         manager.onMediaDataLoaded(KEY, null, mediaData)
         fakeBgExecutor.runAllReady()
@@ -672,13 +725,108 @@
         assertThat(data.showBroadcastButton).isFalse()
     }
 
-    fun captureCallback(): LocalMediaManager.DeviceCallback {
+    // Duplicates of above tests with MEDIA_DEVICE_NAME_FIX enabled
+
+    @Test
+    fun loadMediaDataWithNullToken_withNameFix() {
+        featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
+        manager.onMediaDataLoaded(KEY, null, mediaData.copy(token = null))
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        val data = captureDeviceData(KEY)
+        assertThat(data.enabled).isTrue()
+        assertThat(data.name).isEqualTo(DEVICE_NAME)
+    }
+
+    @Test
+    fun onAboutToConnectDeviceAdded_findsDeviceInfoFromAddress_withNameFix() {
+        featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        // Run and reset the executors and listeners so we only focus on new events.
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        reset(listener)
+
+        // Ensure we'll get device info when using the address
+        val fullMediaDevice = mock(MediaDevice::class.java)
+        val address = "fakeAddress"
+        val nameFromDevice = "nameFromDevice"
+        val iconFromDevice = mock(Drawable::class.java)
+        whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(fullMediaDevice)
+        whenever(fullMediaDevice.name).thenReturn(nameFromDevice)
+        whenever(fullMediaDevice.iconWithoutBackground).thenReturn(iconFromDevice)
+
+        // WHEN the about-to-connect device changes to non-null
+        val deviceCallback = captureCallback()
+        val nameFromParam = "nameFromParam"
+        val iconFromParam = mock(Drawable::class.java)
+        deviceCallback.onAboutToConnectDeviceAdded(address, nameFromParam, iconFromParam)
+        assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
+
+        // THEN the about-to-connect device based on the address is returned
+        val data = captureDeviceData(KEY)
+        assertThat(data.enabled).isTrue()
+        assertThat(data.name).isEqualTo(nameFromDevice)
+        assertThat(data.name).isNotEqualTo(nameFromParam)
+        assertThat(data.icon).isEqualTo(iconFromDevice)
+        assertThat(data.icon).isNotEqualTo(iconFromParam)
+    }
+
+    @Test
+    fun deviceNameFromMR2RouteInfo_withNameFix() {
+        featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
+        // GIVEN that MR2Manager returns a valid routing session
+        whenever(route.name).thenReturn(REMOTE_DEVICE_NAME)
+        // WHEN a notification is added
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        // THEN it uses the route name (instead of device name)
+        val data = captureDeviceData(KEY)
+        assertThat(data.enabled).isTrue()
+        assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME)
+    }
+
+    @Test
+    fun deviceDisabledWhenMR2ReturnsNullRouteInfo_withNameFix() {
+        featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
+        // GIVEN that MR2Manager returns null for routing session
+        whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
+        // WHEN a notification is added
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        // THEN the device is disabled and name is set to null
+        val data = captureDeviceData(KEY)
+        assertThat(data.enabled).isFalse()
+        assertThat(data.name).isNull()
+    }
+
+    @Test
+    fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName_withNameFix() {
+        featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
+        // GIVEN that MR2Manager returns a routing session that does not have a name
+        whenever(route.name).thenReturn(null)
+        whenever(route.isSystemSession).thenReturn(false)
+        // WHEN a notification is added
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        // THEN the device is enabled and uses the current connected device name
+        val data = captureDeviceData(KEY)
+        assertThat(data.name).isEqualTo(DEVICE_NAME)
+        assertThat(data.enabled).isTrue()
+    }
+
+    // End duplicate tests
+
+    private fun captureCallback(): LocalMediaManager.DeviceCallback {
         val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java)
         verify(lmm).registerCallback(captor.capture())
         return captor.getValue()
     }
 
-    fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback {
+    private fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback {
         val callback: BluetoothLeBroadcast.Callback =
             object : BluetoothLeBroadcast.Callback {
                 override fun onBroadcastStarted(reason: Int, broadcastId: Int) {}
@@ -699,7 +847,7 @@
         return callback
     }
 
-    fun setupLeAudioConfiguration(isLeAudio: Boolean) {
+    private fun setupLeAudioConfiguration(isLeAudio: Boolean) {
         whenever(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager)
         whenever(localBluetoothProfileManager.leAudioBroadcastProfile)
             .thenReturn(localBluetoothLeBroadcast)
@@ -707,7 +855,7 @@
         whenever(localBluetoothLeBroadcast.appSourceName).thenReturn(BROADCAST_APP_NAME)
     }
 
-    fun setupBroadcastPackage(currentName: String) {
+    private fun setupBroadcastPackage(currentName: String) {
         whenever(lmm.packageName).thenReturn(PACKAGE)
         whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt()))
             .thenReturn(applicationInfo)
@@ -715,7 +863,7 @@
         context.setMockPackageManager(packageManager)
     }
 
-    fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData {
+    private fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData {
         val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java)
         verify(listener).onMediaDeviceChanged(eq(key), eq(oldKey), captor.capture())
         return captor.getValue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index de57b60..5296f1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -24,7 +24,6 @@
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardViewController
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
 import com.android.systemui.dreams.DreamOverlayStateController
@@ -32,6 +31,7 @@
 import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.media.dream.MediaDreamComplication
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
 import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index f25cd24..34360d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -7,12 +7,16 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
+import com.android.systemui.shared.recents.model.ThumbnailData
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -34,6 +38,8 @@
     private val view: MediaProjectionAppSelectorView = mock()
     private val policyResolver: ScreenCaptureDevicePolicyResolver = mock()
 
+    private val thumbnailLoader = FakeThumbnailLoader()
+
     private val controller =
         MediaProjectionAppSelectorController(
             taskListProvider,
@@ -42,7 +48,8 @@
             personalUserHandle,
             scope,
             appSelectorComponentName,
-            callerPackageName
+            callerPackageName,
+            thumbnailLoader,
         )
 
     @Before
@@ -69,6 +76,22 @@
     }
 
     @Test
+    fun init_refreshesThumbnailsOfForegroundTasks() = runTest {
+        val tasks =
+            listOf(
+                createRecentTask(taskId = 1, isForegroundTask = false),
+                createRecentTask(taskId = 2, isForegroundTask = true),
+                createRecentTask(taskId = 3, isForegroundTask = true),
+                createRecentTask(taskId = 4, isForegroundTask = false),
+            )
+        taskListProvider.tasks = tasks
+
+        controller.init()
+
+        assertThat(thumbnailLoader.capturedTaskIds).containsExactly(2, 3)
+    }
+
+    @Test
     fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInTheSameOrder() {
         val tasks =
             listOf(
@@ -188,14 +211,16 @@
     private fun createRecentTask(
         taskId: Int,
         topActivityComponent: ComponentName? = null,
-        userId: Int = personalUserHandle.identifier
+        userId: Int = personalUserHandle.identifier,
+        isForegroundTask: Boolean = false
     ): RecentTask {
         return RecentTask(
             taskId = taskId,
             topActivityComponent = topActivityComponent,
             baseIntentComponent = ComponentName("com", "Test"),
             userId = userId,
-            colorBackground = 0
+            colorBackground = 0,
+            isForegroundTask = isForegroundTask,
         )
     }
 
@@ -205,4 +230,18 @@
 
         override suspend fun loadRecentTasks(): List<RecentTask> = tasks
     }
+
+    private class FakeThumbnailLoader : RecentTaskThumbnailLoader {
+
+        val capturedTaskIds = mutableListOf<Int>()
+
+        override suspend fun loadThumbnail(taskId: Int): ThumbnailData? {
+            return null
+        }
+
+        override suspend fun captureThumbnail(taskId: Int): ThumbnailData? {
+            capturedTaskIds += taskId
+            return null
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
new file mode 100644
index 0000000..db275ec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
@@ -0,0 +1,109 @@
+package com.android.systemui.mediaprojection.appselector.data
+
+import android.app.WindowConfiguration
+import android.content.ComponentName
+import android.content.res.Configuration
+import android.graphics.ColorSpace
+import android.graphics.Point
+import android.graphics.Rect
+import android.hardware.HardwareBuffer
+import android.testing.AndroidTestingRunner
+import android.view.Surface
+import android.window.TaskSnapshot
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class ActivityTaskManagerThumbnailLoaderTest : SysuiTestCase() {
+
+    private val dispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+    private val activityManager = mock<ActivityManagerWrapper>()
+    private val loader = ActivityTaskManagerThumbnailLoader(dispatcher, activityManager)
+
+    @Test
+    fun loadThumbnail_emptyThumbnail_returnsNull() =
+        testScope.runTest {
+            val taskId = 123
+            val isLowResolution = false
+            val thumbnailData = ThumbnailData()
+            whenever(activityManager.getTaskThumbnail(taskId, isLowResolution))
+                .thenReturn(thumbnailData)
+
+            assertThat(loader.loadThumbnail(taskId)).isNull()
+        }
+
+    @Test
+    fun loadThumbnail_thumbnailAvailable_returnsThumbnailData() =
+        testScope.runTest {
+            val taskId = 123
+            val isLowResolution = false
+            val snapshot = createTaskSnapshot()
+            val thumbnailData = ThumbnailData(snapshot)
+            whenever(activityManager.getTaskThumbnail(taskId, isLowResolution))
+                .thenReturn(thumbnailData)
+
+            assertThat(loader.loadThumbnail(taskId)).isEqualTo(thumbnailData)
+        }
+
+    @Test
+    fun captureThumbnail_emptyThumbnail_returnsNull() =
+        testScope.runTest {
+            val taskId = 321
+            val emptyThumbnailData = ThumbnailData()
+
+            whenever(activityManager.takeTaskThumbnail(taskId)).thenReturn(emptyThumbnailData)
+
+            assertThat(loader.captureThumbnail(taskId)).isNull()
+        }
+
+    @Test
+    fun captureThumbnail_thumbnailAvailable_returnsThumbnailData() =
+        testScope.runTest {
+            val taskId = 321
+            val thumbnailData = ThumbnailData(createTaskSnapshot())
+
+            whenever(activityManager.takeTaskThumbnail(taskId)).thenReturn(thumbnailData)
+
+            assertThat(loader.captureThumbnail(taskId)).isEqualTo(thumbnailData)
+        }
+
+    private fun createTaskSnapshot() =
+        TaskSnapshot(
+            /* id= */ 123,
+            /* captureTime= */ 0,
+            /* topActivityComponent= */ ComponentName("package", "class"),
+            /* snapshot= */ HardwareBuffer.create(
+                /* width= */ 100,
+                /* height= */ 100,
+                HardwareBuffer.RGBA_8888,
+                /* layers= */ 1,
+                /* usage= */ HardwareBuffer.USAGE_CPU_READ_OFTEN
+            ),
+            ColorSpace.get(ColorSpace.Named.SRGB),
+            Configuration.ORIENTATION_PORTRAIT,
+            Surface.ROTATION_0,
+            /* taskSize= */ Point(100, 100),
+            /* contentInsets= */ Rect(),
+            /* letterboxInsets= */ Rect(),
+            /* isLowResolution= */ false,
+            /* isRealSnapshot= */ true,
+            WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
+            /* appearance= */ 0,
+            /* isTranslucent= */ false,
+            /* hasImeSurface= */ false
+        )
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index d35a212..2c7ee56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -11,7 +11,7 @@
 import com.android.wm.shell.recents.RecentTasks
 import com.android.wm.shell.util.GroupedRecentTaskInfo
 import com.google.common.truth.Truth.assertThat
-import java.util.*
+import java.util.Optional
 import java.util.function.Consumer
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
@@ -52,12 +52,7 @@
 
         val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
 
-        assertThat(result)
-            .containsExactly(
-                createRecentTask(taskId = 1),
-                createRecentTask(taskId = 2),
-                createRecentTask(taskId = 3),
-            )
+        assertThat(result.map { it.taskId }).containsExactly(1, 2, 3).inOrder()
     }
 
     @Test
@@ -66,8 +61,7 @@
 
         val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
 
-        assertThat(result)
-            .containsExactly(createRecentTask(taskId = 1), createRecentTask(taskId = 2))
+        assertThat(result.map { it.taskId }).containsExactly(1, 2).inOrder()
     }
 
     @Test
@@ -81,15 +75,46 @@
 
         val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
 
-        assertThat(result)
-            .containsExactly(
-                createRecentTask(taskId = 1),
-                createRecentTask(taskId = 2),
-                createRecentTask(taskId = 3),
-                createRecentTask(taskId = 4),
-                createRecentTask(taskId = 5),
-                createRecentTask(taskId = 6),
-            )
+        assertThat(result.map { it.taskId }).containsExactly(1, 2, 3, 4, 5, 6).inOrder()
+    }
+
+    @Test
+    fun loadRecentTasks_singleTask_returnsTaskAsNotForeground() {
+        givenRecentTasks(
+            createSingleTask(taskId = 1),
+        )
+
+        val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+        assertThat(result[0].isForegroundTask).isFalse()
+    }
+
+    @Test
+    fun loadRecentTasks_multipleTasks_returnsSecondTaskAsForegroundTask() {
+        givenRecentTasks(
+            createSingleTask(taskId = 1),
+            createSingleTask(taskId = 2),
+            createSingleTask(taskId = 3),
+        )
+
+        val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+        assertThat(result.map { it.isForegroundTask }).containsExactly(false, true, false).inOrder()
+    }
+
+    @Test
+    fun loadRecentTasks_secondTaskIsGrouped_marksBothGroupedTasksAsForeground() {
+        givenRecentTasks(
+            createSingleTask(taskId = 1),
+            createTaskPair(taskId1 = 2, taskId2 = 3),
+            createSingleTask(taskId = 4),
+        )
+
+        val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+        assertThat(result.map { it.isForegroundTask })
+            .containsExactly(false, true, true, false)
+            .inOrder()
     }
 
     @Suppress("UNCHECKED_CAST")
@@ -106,7 +131,8 @@
             userId = 0,
             topActivityComponent = null,
             baseIntentComponent = null,
-            colorBackground = null
+            colorBackground = null,
+            isForegroundTask = false,
         )
 
     private fun createSingleTask(taskId: Int): GroupedRecentTaskInfo =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
index 906420d..9630ee3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
@@ -46,6 +46,7 @@
     private val mockContext = mock<Context>()
     private val resources = mock<Resources>()
     private val windowManager = mock<WindowManager>()
+    private val windowMetricsProvider = mock<WindowMetricsProvider>()
     private val sizeUpdates = arrayListOf<Rect>()
     private val testConfigurationController = FakeConfigurationController()
 
@@ -112,13 +113,12 @@
     }
 
     private fun givenTaskbarSize(size: Int) {
-        val windowInsets =
-            WindowInsets.Builder()
-                .setInsets(Type.tappableElement(), Insets.of(Rect(0, 0, 0, size)))
-                .build()
+        val insets = Insets.of(Rect(0, 0, 0, size))
+        val windowInsets = WindowInsets.Builder().setInsets(Type.tappableElement(), insets).build()
         val windowMetrics = WindowMetrics(windowManager.maximumWindowMetrics.bounds, windowInsets)
         whenever(windowManager.maximumWindowMetrics).thenReturn(windowMetrics)
         whenever(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
+        whenever(windowMetricsProvider.currentWindowInsets).thenReturn(insets)
     }
 
     private fun givenDisplay(width: Int, height: Int, isTablet: Boolean = false) {
@@ -126,6 +126,7 @@
         val windowMetrics = WindowMetrics(bounds, { null }, 1.0f)
         whenever(windowManager.maximumWindowMetrics).thenReturn(windowMetrics)
         whenever(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
+        whenever(windowMetricsProvider.maximumWindowBounds).thenReturn(bounds)
 
         val minDimension = min(width, height)
 
@@ -147,7 +148,11 @@
                 }
             }
 
-        return TaskPreviewSizeProvider(mockContext, windowManager, testConfigurationController)
+        return TaskPreviewSizeProvider(
+                mockContext,
+                windowMetricsProvider,
+                testConfigurationController
+            )
             .also { it.addCallback(listener) }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProviderImplTest.kt
new file mode 100644
index 0000000..fe18454
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/WindowMetricsProviderImplTest.kt
@@ -0,0 +1,44 @@
+package com.android.systemui.mediaprojection.appselector.view
+
+import android.graphics.Insets
+import android.graphics.Rect
+import android.view.WindowManager
+import android.view.WindowMetrics
+import androidx.core.view.WindowInsetsCompat
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class WindowMetricsProviderImplTest : SysuiTestCase() {
+
+    private val windowManager = mock<WindowManager>()
+    private val windowMetricsProvider = WindowMetricsProviderImpl(windowManager)
+
+    @Test
+    fun getMaximumWindowBounds_returnsValueFromWMMaxWindowMetrics() {
+        val bounds = Rect(/* left= */ 100, /* top= */ 200, /* right= */ 300, /* bottom= */ 400)
+        val metrics =
+            WindowMetrics(bounds, /* windowInsetsSupplier= */ { null }, /* density= */ 1.0f)
+        whenever(windowManager.maximumWindowMetrics).thenReturn(metrics)
+
+        assertThat(windowMetricsProvider.maximumWindowBounds).isEqualTo(bounds)
+    }
+
+    @Test
+    fun getCurrentWindowInsets_returnsFromWMCurrentWindowMetrics() {
+        val bounds = Rect()
+        val insets =
+            Insets.of(Rect(/* left= */ 123, /* top= */ 456, /* right= */ 789, /* bottom= */ 1012))
+        val windowInsets =
+            android.view.WindowInsets.Builder()
+                .setInsets(WindowInsetsCompat.Type.tappableElement(), insets)
+                .build()
+        whenever(windowManager.currentWindowMetrics).thenReturn(WindowMetrics(bounds, windowInsets))
+
+        assertThat(windowMetricsProvider.currentWindowInsets).isEqualTo(insets)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 8019fb5..6248bb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -57,7 +57,7 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.NotificationChannels;
-import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.FakeGlobalSettings;
 import com.android.systemui.util.settings.GlobalSettings;
 
 import org.junit.Before;
@@ -77,7 +77,7 @@
     public static final String FORMATTED_45M = "0h 45m";
     public static final String FORMATTED_HOUR = "1h 0m";
     private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
-    private final GlobalSettings mGlobalSettings = new FakeSettings();
+    private final GlobalSettings mGlobalSettings = new FakeGlobalSettings();
     private PowerNotificationWarnings mPowerNotificationWarnings;
 
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/SettingObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt
similarity index 88%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/SettingObserverTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt
index 4be6890..8f06fe2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/SettingObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt
@@ -35,7 +35,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
-class SettingObserverTest : SysuiTestCase() {
+class UserSettingObserverTest : SysuiTestCase() {
 
     companion object {
         private const val TEST_SETTING = "setting"
@@ -46,7 +46,7 @@
     }
 
     private lateinit var testableLooper: TestableLooper
-    private lateinit var setting: SettingObserver
+    private lateinit var setting: UserSettingObserver
     private lateinit var secureSettings: SecureSettings
 
     private lateinit var callback: Callback
@@ -56,17 +56,19 @@
         testableLooper = TestableLooper.get(this)
         secureSettings = FakeSettings()
 
-        setting = object : SettingObserver(
-                secureSettings,
-                Handler(testableLooper.looper),
-                TEST_SETTING,
-                USER,
-                DEFAULT_VALUE
-        ) {
-            override fun handleValueChanged(value: Int, observedChange: Boolean) {
-                callback(value, observedChange)
+        setting =
+            object :
+                UserSettingObserver(
+                    secureSettings,
+                    Handler(testableLooper.looper),
+                    TEST_SETTING,
+                    USER,
+                    DEFAULT_VALUE
+                ) {
+                override fun handleValueChanged(value: Int, observedChange: Boolean) {
+                    callback(value, observedChange)
+                }
             }
-        }
 
         // Default empty callback
         callback = { _, _ -> Unit }
@@ -162,4 +164,4 @@
         setting.isListening = true
         assertThat(setting.value).isEqualTo(DEFAULT_VALUE)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index 67587e3..6cc52d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -29,12 +29,14 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doAnswer;
 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.compat.CompatChanges;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -73,16 +75,18 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class TileLifecycleManagerTest extends SysuiTestCase {
-    private static final int TEST_FAIL_TIMEOUT = 5000;
 
     private final PackageManagerAdapter mMockPackageManagerAdapter =
             mock(PackageManagerAdapter.class);
     private final BroadcastDispatcher mMockBroadcastDispatcher =
             mock(BroadcastDispatcher.class);
     private final IQSTileService.Stub mMockTileService = mock(IQSTileService.Stub.class);
+    private final ActivityManager mActivityManager = mock(ActivityManager.class);
+
     private ComponentName mTileServiceComponentName;
     private Intent mTileServiceIntent;
     private UserHandle mUser;
+    private FakeSystemClock mClock;
     private FakeExecutor mExecutor;
     private HandlerThread mThread;
     private Handler mHandler;
@@ -112,13 +116,15 @@
         mThread = new HandlerThread("TestThread");
         mThread.start();
         mHandler = Handler.createAsync(mThread.getLooper());
-        mExecutor = new FakeExecutor(new FakeSystemClock());
+        mClock = new FakeSystemClock();
+        mExecutor = new FakeExecutor(mClock);
         mStateManager = new TileLifecycleManager(mHandler, mWrappedContext,
                 mock(IQSService.class),
                 mMockPackageManagerAdapter,
                 mMockBroadcastDispatcher,
                 mTileServiceIntent,
                 mUser,
+                mActivityManager,
                 mExecutor);
     }
 
@@ -294,11 +300,32 @@
         mStateManager.onStartListening();
         mStateManager.executeSetBindService(true);
         mExecutor.runAllReady();
-        mStateManager.setBindRetryDelay(0);
+        mStateManager.onServiceDisconnected(mTileServiceComponentName);
+        mClock.advanceTime(5000);
+
+        // Two calls: one for the first bind, one for the restart.
+        verifyBind(2);
+        verify(mMockTileService, times(2)).onStartListening();
+    }
+
+    @Test
+    public void testKillProcessLowMemory() throws Exception {
+        doAnswer(invocation -> {
+            ActivityManager.MemoryInfo memoryInfo = invocation.getArgument(0);
+            memoryInfo.lowMemory = true;
+            return null;
+        }).when(mActivityManager).getMemoryInfo(any());
+        mStateManager.onStartListening();
+        mStateManager.executeSetBindService(true);
         mExecutor.runAllReady();
         mStateManager.onServiceDisconnected(mTileServiceComponentName);
-        mExecutor.runAllReady();
 
+        // Longer delay than a regular one
+        mClock.advanceTime(5000);
+        verifyBind(1);
+        verify(mMockTileService, times(1)).onStartListening();
+
+        mClock.advanceTime(20000);
         // Two calls: one for the first bind, one for the restart.
         verifyBind(2);
         verify(mMockTileService, times(2)).onStartListening();
@@ -319,6 +346,7 @@
                 mMockBroadcastDispatcher,
                 mTileServiceIntent,
                 mUser,
+                mActivityManager,
                 mExecutor);
 
         manager.executeSetBindService(true);
@@ -340,6 +368,7 @@
                 mMockBroadcastDispatcher,
                 mTileServiceIntent,
                 mUser,
+                mActivityManager,
                 mExecutor);
 
         manager.executeSetBindService(true);
@@ -361,6 +390,7 @@
                 mMockBroadcastDispatcher,
                 mTileServiceIntent,
                 mUser,
+                mActivityManager,
                 mExecutor);
 
         manager.executeSetBindService(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 4bc16a5..d011821 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -304,7 +304,7 @@
                 CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) {
             super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController,
                     commandQueue, statusBarIconController, panelInteractor,
-                    customTileAddedRepository, executor);
+                    mTileLifecycleManagerFactory, customTileAddedRepository, executor);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 4ada44c..9b1f830 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -25,7 +25,6 @@
 import androidx.test.filters.SmallTest
 import com.android.settingslib.Utils
 import com.android.settingslib.drawable.UserIconDrawable
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.shared.model.ContentDescription
@@ -35,6 +34,7 @@
 import com.android.systemui.qs.QSSecurityFooterUtils
 import com.android.systemui.qs.footer.FooterActionsTestUtils
 import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
+import com.android.systemui.res.R
 import com.android.systemui.security.data.model.SecurityModel
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.statusbar.policy.FakeSecurityController
@@ -44,7 +44,7 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.nullable
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.FakeGlobalSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.launch
@@ -128,7 +128,7 @@
     fun userSwitcher() = runTest {
         val picture: Drawable = mock()
         val userInfoController = FakeUserInfoController(FakeInfo(picture = picture))
-        val settings = FakeSettings()
+        val settings = FakeGlobalSettings()
         val userId = 42
         val userTracker = FakeUserTracker(userId)
         val userSwitcherControllerWrapper =
@@ -167,14 +167,9 @@
         // for the current user will be fired to notify us of that change.
         isUserSwitcherEnabled = true
 
-        // Update the setting for a random user: nothing should change, given that at this point we
-        // weren't notified of the change yet.
-        utils.setUserSwitcherEnabled(settings, true, 3)
-        assertThat(currentUserSwitcher()).isNull()
-
         // Update the setting for the observed user: now we will be notified and the button should
         // be there.
-        utils.setUserSwitcherEnabled(settings, true, userId)
+        utils.setUserSwitcherEnabled(settings, true)
         val userSwitcher = currentUserSwitcher()
         assertThat(userSwitcher).isNotNull()
         assertThat(userSwitcher!!.icon)
@@ -372,9 +367,7 @@
                     ),
             )
 
-        val job = launch {
-            underTest.observeDeviceMonitoringDialogRequests(quickSettingsContext = mock())
-        }
+        val job = launch { underTest.observeDeviceMonitoringDialogRequests(mock()) }
 
         advanceUntilIdle()
         assertThat(nDialogRequests).isEqualTo(3)
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/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
index a0ff2ab..dcda005 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.test.TestCoroutineScheduler
@@ -95,8 +96,7 @@
                 testScope.backgroundScope,
                 dispatcher,
             )
-        `when`(deviceItemInteractor.deviceItemUpdate)
-            .thenReturn(MutableStateFlow(null).asStateFlow())
+        `when`(deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
         `when`(bluetoothStateInteractor.bluetoothStateUpdate)
             .thenReturn(MutableStateFlow(null).asStateFlow())
         `when`(deviceItemInteractor.deviceItemUpdateRequest)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
index 3593075..428f79c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
@@ -27,10 +27,11 @@
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Rule
@@ -78,7 +79,7 @@
 
     @Before
     fun setUp() {
-        dispatcher = StandardTestDispatcher()
+        dispatcher = UnconfinedTestDispatcher()
         testScope = TestScope(dispatcher)
         interactor =
             DeviceItemInteractor(
@@ -107,9 +108,10 @@
                 listOf(createFactory({ true }, deviceItem1))
             )
 
+            val latest by collectLastValue(interactor.deviceItemUpdate)
             interactor.updateDeviceItems(mContext)
 
-            assertThat(interactor.deviceItemUpdate.value).isEmpty()
+            assertThat(latest).isEqualTo(emptyList<DeviceItem>())
         }
     }
 
@@ -121,9 +123,10 @@
                 listOf(createFactory({ false }, deviceItem1))
             )
 
+            val latest by collectLastValue(interactor.deviceItemUpdate)
             interactor.updateDeviceItems(mContext)
 
-            assertThat(interactor.deviceItemUpdate.value).isEmpty()
+            assertThat(latest).isEqualTo(emptyList<DeviceItem>())
         }
     }
 
@@ -135,10 +138,10 @@
                 listOf(createFactory({ true }, deviceItem1))
             )
 
+            val latest by collectLastValue(interactor.deviceItemUpdate)
             interactor.updateDeviceItems(mContext)
 
-            assertThat(interactor.deviceItemUpdate.value).hasSize(1)
-            assertThat(interactor.deviceItemUpdate.value!![0]).isEqualTo(deviceItem1)
+            assertThat(latest).isEqualTo(listOf(deviceItem1))
         }
     }
 
@@ -150,11 +153,10 @@
                 listOf(createFactory({ false }, deviceItem1), createFactory({ true }, deviceItem2))
             )
 
+            val latest by collectLastValue(interactor.deviceItemUpdate)
             interactor.updateDeviceItems(mContext)
 
-            assertThat(interactor.deviceItemUpdate.value).hasSize(2)
-            assertThat(interactor.deviceItemUpdate.value!![0]).isEqualTo(deviceItem2)
-            assertThat(interactor.deviceItemUpdate.value!![1]).isEqualTo(deviceItem2)
+            assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem2))
         }
     }
 
@@ -177,10 +179,10 @@
             `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
             `when`(deviceItem2.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE)
 
+            val latest by collectLastValue(interactor.deviceItemUpdate)
             interactor.updateDeviceItems(mContext)
 
-            assertThat(interactor.deviceItemUpdate.value)
-                .isEqualTo(listOf(deviceItem2, deviceItem1))
+            assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem1))
         }
     }
 
@@ -200,10 +202,10 @@
             `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
             `when`(deviceItem2.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
 
+            val latest by collectLastValue(interactor.deviceItemUpdate)
             interactor.updateDeviceItems(mContext)
 
-            assertThat(interactor.deviceItemUpdate.value)
-                .isEqualTo(listOf(deviceItem2, deviceItem1))
+            assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem1))
         }
     }
 
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/retail/data/repository/RetailModeSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
index 421c355..fe80f70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
@@ -21,7 +21,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.FakeGlobalSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -36,7 +36,7 @@
 @RunWith(AndroidTestingRunner::class)
 class RetailModeSettingsRepositoryTest : SysuiTestCase() {
 
-    private val globalSettings = FakeSettings()
+    private val globalSettings = FakeGlobalSettings()
 
     private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
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 2e16577..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
@@ -52,7 +54,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
@@ -138,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(
@@ -150,6 +152,7 @@
                     FakeConnectivityRepository(),
                 ),
             constants = mock(),
+            flags,
             scope = testScope.backgroundScope,
         )
 
@@ -463,8 +466,7 @@
                 fromScene = getCurrentSceneInUi(),
                 toScene = to.key,
                 progress = progressFlow,
-                isInitiatedByUserInput = false,
-                isUserInputOngoing = flowOf(false),
+                isUserInputDriven = false,
             )
         runCurrent()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 740c6d9..432bd0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -29,7 +29,6 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -120,8 +119,7 @@
                     fromScene = SceneKey.Lockscreen,
                     toScene = SceneKey.Shade,
                     progress = progress,
-                    isInitiatedByUserInput = false,
-                    isUserInputOngoing = flowOf(false),
+                    isUserInputDriven = false,
                 )
             assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 31d26c0..8b23d18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -83,8 +83,7 @@
                     fromScene = SceneKey.Lockscreen,
                     toScene = SceneKey.Shade,
                     progress = progress,
-                    isInitiatedByUserInput = false,
-                    isUserInputOngoing = flowOf(false),
+                    isUserInputDriven = false,
                 )
             assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
 
@@ -122,8 +121,7 @@
                     fromScene = underTest.desiredScene.value.key,
                     toScene = SceneKey.Shade,
                     progress = progress,
-                    isInitiatedByUserInput = false,
-                    isUserInputOngoing = flowOf(false),
+                    isUserInputDriven = false,
                 )
             assertThat(transitionTo).isEqualTo(SceneKey.Shade)
 
@@ -160,8 +158,7 @@
                         fromScene = SceneKey.Gone,
                         toScene = SceneKey.Lockscreen,
                         progress = flowOf(0.5f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             val transitioning by
@@ -180,8 +177,7 @@
                         fromScene = SceneKey.Shade,
                         toScene = SceneKey.QuickSettings,
                         progress = flowOf(0.5f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             underTest.setTransitionState(transitionState)
@@ -198,8 +194,7 @@
                         fromScene = SceneKey.Shade,
                         toScene = SceneKey.Lockscreen,
                         progress = flowOf(0.5f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             val transitioning by
@@ -227,8 +222,7 @@
                     fromScene = SceneKey.Shade,
                     toScene = SceneKey.Lockscreen,
                     progress = flowOf(0.5f),
-                    isInitiatedByUserInput = false,
-                    isUserInputOngoing = flowOf(false),
+                    isUserInputDriven = false,
                 )
             assertThat(transitioning).isTrue()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 3b9621e..7b13de6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -109,8 +109,7 @@
                     fromScene = SceneKey.Gone,
                     toScene = SceneKey.Shade,
                     progress = flowOf(0.5f),
-                    isInitiatedByUserInput = false,
-                    isUserInputOngoing = flowOf(false),
+                    isUserInputDriven = false,
                 )
             assertThat(isVisible).isTrue()
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
@@ -123,8 +122,7 @@
                     fromScene = SceneKey.Shade,
                     toScene = SceneKey.Gone,
                     progress = flowOf(0.5f),
-                    isInitiatedByUserInput = false,
-                    isUserInputOngoing = flowOf(false),
+                    isUserInputDriven = false,
                 )
             assertThat(isVisible).isTrue()
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
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/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 0d694ee..7e41745 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -28,7 +28,6 @@
 import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
 import com.android.internal.util.ScreenshotRequest
-import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
@@ -47,7 +46,6 @@
 
     private val scope = CoroutineScope(Dispatchers.Unconfined)
     private val policy = FakeScreenshotPolicy()
-    private val flags = FakeFeatureFlags()
 
     /** Tests the Java-compatible function wrapper, ensures callback is invoked. */
     @Test
@@ -58,7 +56,7 @@
                     .setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
                     .build()
             )
-        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+        val processor = RequestProcessor(imageCapture, policy, scope)
 
         var result: ScreenshotData? = null
         var callbackCount = 0
@@ -86,7 +84,7 @@
 
         val request =
             ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build()
-        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+        val processor = RequestProcessor(imageCapture, policy, scope)
 
         val processedData = processor.process(ScreenshotData.fromRequest(request))
 
@@ -111,7 +109,7 @@
 
         val request =
             ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
-        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+        val processor = RequestProcessor(imageCapture, policy, scope)
 
         val processedData = processor.process(ScreenshotData.fromRequest(request))
 
@@ -138,7 +136,7 @@
 
         val request =
             ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
-        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+        val processor = RequestProcessor(imageCapture, policy, scope)
 
         Assert.assertThrows(IllegalStateException::class.java) {
             runBlocking { processor.process(ScreenshotData.fromRequest(request)) }
@@ -148,7 +146,7 @@
     @Test
     fun testProvidedImageScreenshot() = runBlocking {
         val bounds = Rect(50, 50, 150, 150)
-        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+        val processor = RequestProcessor(imageCapture, policy, scope)
 
         policy.setManagedProfile(USER_ID, false)
 
@@ -173,7 +171,7 @@
     @Test
     fun testProvidedImageScreenshot_managedProfile() = runBlocking {
         val bounds = Rect(50, 50, 150, 150)
-        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+        val processor = RequestProcessor(imageCapture, policy, scope)
 
         // Indicate that the screenshot belongs to a manged profile
         policy.setManagedProfile(USER_ID, true)
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..3dc9037 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import java.lang.IllegalStateException
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runCurrent
@@ -43,8 +44,11 @@
 
     private val controller0 = mock<ScreenshotController>()
     private val controller1 = mock<ScreenshotController>()
+    private val notificationsController0 = mock<ScreenshotNotificationsController>()
+    private val notificationsController1 = mock<ScreenshotNotificationsController>()
     private val controllerFactory = mock<ScreenshotController.Factory>()
     private val callback = mock<TakeScreenshotService.RequestCallback>()
+    private val notificationControllerFactory = mock<ScreenshotNotificationsController.Factory>()
 
     private val fakeDisplayRepository = FakeDisplayRepository()
     private val requestProcessor = FakeRequestProcessor()
@@ -59,12 +63,15 @@
             testScope,
             requestProcessor,
             eventLogger,
+            notificationControllerFactory
         )
 
     @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)
+        whenever(notificationControllerFactory.create(eq(0))).thenReturn(notificationsController0)
+        whenever(notificationControllerFactory.create(eq(1))).thenReturn(notificationsController1)
     }
 
     @Test
@@ -74,8 +81,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 +114,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 +146,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),
@@ -310,6 +317,123 @@
             screenshotExecutor.onDestroy()
         }
 
+    @Test
+    fun executeScreenshots_errorFromProcessor_logsScreenshotRequested() =
+        testScope.runTest {
+            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+            val onSaved = { _: Uri -> }
+            requestProcessor.shouldThrowException = true
+
+            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+            val screenshotRequested =
+                eventLogger.logs.filter {
+                    it.eventId == ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id
+                }
+            assertThat(screenshotRequested).hasSize(2)
+            screenshotExecutor.onDestroy()
+        }
+
+    @Test
+    fun executeScreenshots_errorFromProcessor_logsUiError() =
+        testScope.runTest {
+            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+            val onSaved = { _: Uri -> }
+            requestProcessor.shouldThrowException = true
+
+            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+            val screenshotRequested =
+                eventLogger.logs.filter {
+                    it.eventId == ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED.id
+                }
+            assertThat(screenshotRequested).hasSize(2)
+            screenshotExecutor.onDestroy()
+        }
+
+    @Test
+    fun executeScreenshots_errorFromProcessorOnDefaultDisplay_showsErrorNotification() =
+        testScope.runTest {
+            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+            val onSaved = { _: Uri -> }
+            requestProcessor.shouldThrowException = true
+
+            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+            verify(notificationsController0).notifyScreenshotError(any())
+            screenshotExecutor.onDestroy()
+        }
+
+    @Test
+    fun executeScreenshots_errorFromProcessorOnSecondaryDisplay_showsErrorNotification() =
+        testScope.runTest {
+            setDisplays(display(TYPE_INTERNAL, id = 0))
+            val onSaved = { _: Uri -> }
+            requestProcessor.shouldThrowException = true
+
+            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+            verify(notificationsController0).notifyScreenshotError(any())
+            screenshotExecutor.onDestroy()
+        }
+
+    @Test
+    fun executeScreenshots_errorFromScreenshotController_reportsRequested() =
+        testScope.runTest {
+            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+            val onSaved = { _: Uri -> }
+            whenever(controller0.handleScreenshot(any(), any(), any()))
+                .thenThrow(IllegalStateException::class.java)
+            whenever(controller1.handleScreenshot(any(), any(), any()))
+                .thenThrow(IllegalStateException::class.java)
+
+            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+            val screenshotRequested =
+                eventLogger.logs.filter {
+                    it.eventId == ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id
+                }
+            assertThat(screenshotRequested).hasSize(2)
+            screenshotExecutor.onDestroy()
+        }
+
+    @Test
+    fun executeScreenshots_errorFromScreenshotController_reportsError() =
+        testScope.runTest {
+            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+            val onSaved = { _: Uri -> }
+            whenever(controller0.handleScreenshot(any(), any(), any()))
+                .thenThrow(IllegalStateException::class.java)
+            whenever(controller1.handleScreenshot(any(), any(), any()))
+                .thenThrow(IllegalStateException::class.java)
+
+            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+            val screenshotRequested =
+                eventLogger.logs.filter {
+                    it.eventId == ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED.id
+                }
+            assertThat(screenshotRequested).hasSize(2)
+            screenshotExecutor.onDestroy()
+        }
+
+    @Test
+    fun executeScreenshots_errorFromScreenshotController_showsErrorNotification() =
+        testScope.runTest {
+            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+            val onSaved = { _: Uri -> }
+            whenever(controller0.handleScreenshot(any(), any(), any()))
+                .thenThrow(IllegalStateException::class.java)
+            whenever(controller1.handleScreenshot(any(), any(), any()))
+                .thenThrow(IllegalStateException::class.java)
+
+            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+            verify(notificationsController0).notifyScreenshotError(any())
+            verify(notificationsController1).notifyScreenshotError(any())
+            screenshotExecutor.onDestroy()
+        }
+
     private suspend fun TestScope.setDisplays(vararg displays: Display) {
         fakeDisplayRepository.emit(displays.toSet())
         runCurrent()
@@ -328,8 +452,9 @@
     private class FakeRequestProcessor : ScreenshotRequestProcessor {
         var processed: ScreenshotData? = null
         var toReturn: ScreenshotData? = null
-
+        var shouldThrowException = false
         override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
+            if (shouldThrowException) throw RequestProcessorException("")
             processed = screenshot
             return toReturn ?: screenshot
         }
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..f3809aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -65,6 +65,7 @@
     private val requestProcessor = mock<RequestProcessor>()
     private val devicePolicyManager = mock<DevicePolicyManager>()
     private val devicePolicyResourcesManager = mock<DevicePolicyResourcesManager>()
+    private val notificationsControllerFactory = mock<ScreenshotNotificationsController.Factory>()
     private val notificationsController = mock<ScreenshotNotificationsController>()
     private val callback = mock<RequestCallback>()
 
@@ -86,7 +87,8 @@
             )
             .thenReturn(false)
         whenever(userManager.isUserUnlocked).thenReturn(true)
-        whenever(controllerFactory.create(any())).thenReturn(controller)
+        whenever(controllerFactory.create(any(), any())).thenReturn(controller)
+        whenever(notificationsControllerFactory.create(any())).thenReturn(notificationsController)
 
         // Stub request processor as a synchronous no-op for tests with the flag enabled
         doAnswer {
@@ -323,7 +325,7 @@
                 userManager,
                 devicePolicyManager,
                 eventLogger,
-                notificationsController,
+                notificationsControllerFactory,
                 mContext,
                 Runnable::run,
                 flags,
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..6223e25 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;
@@ -388,6 +390,7 @@
                 mFeatureFlags,
                 mInteractionJankMonitor,
                 mKeyguardInteractor,
+                mKeyguardTransitionInteractor,
                 mDumpManager,
                 mPowerInteractor));
 
@@ -667,7 +670,8 @@
                 mKeyguardViewConfigurator,
                 mKeyguardFaceAuthInteractor,
                 new ResourcesSplitShadeStateController(),
-                mPowerInteractor);
+                mPowerInteractor,
+                mKeyguardClockPositionAlgorithm);
         mNotificationPanelViewController.initDependencies(
                 mCentralSurfaces,
                 null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index b4f9e8d..677d9db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -49,7 +49,7 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.SystemPropertiesHelper
-import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
+import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
@@ -144,7 +144,7 @@
     @Mock lateinit var dragDownHelper: DragDownHelper
     @Mock
     lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel
-    @Mock lateinit var keyEventInteractor: KeyEventInteractor
+    @Mock lateinit var sysUIKeyEventHandler: SysUIKeyEventHandler
     @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     @Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
     private val notificationExpansionRepository = NotificationExpansionRepository()
@@ -251,7 +251,7 @@
                     securityModel = mock(KeyguardSecurityModel::class.java),
                 ),
                 BouncerLogger(logcatLogBuffer("BouncerLog")),
-                keyEventInteractor,
+                sysUIKeyEventHandler,
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
             )
@@ -475,21 +475,21 @@
     fun forwardsDispatchKeyEvent() {
         val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
         interactionEventHandler.dispatchKeyEvent(keyEvent)
-        verify(keyEventInteractor).dispatchKeyEvent(keyEvent)
+        verify(sysUIKeyEventHandler).dispatchKeyEvent(keyEvent)
     }
 
     @Test
     fun forwardsDispatchKeyEventPreIme() {
         val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
         interactionEventHandler.dispatchKeyEventPreIme(keyEvent)
-        verify(keyEventInteractor).dispatchKeyEventPreIme(keyEvent)
+        verify(sysUIKeyEventHandler).dispatchKeyEventPreIme(keyEvent)
     }
 
     @Test
     fun forwardsInterceptMediaKey() {
         val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP)
         interactionEventHandler.interceptMediaKey(keyEvent)
-        verify(keyEventInteractor).interceptMediaKey(keyEvent)
+        verify(sysUIKeyEventHandler).interceptMediaKey(keyEvent)
     }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 189c9e2..a4a2ca0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -48,7 +48,7 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.SystemPropertiesHelper
-import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
+import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
@@ -253,7 +253,7 @@
                     securityModel = Mockito.mock(KeyguardSecurityModel::class.java),
                 ),
                 BouncerLogger(logcatLogBuffer("BouncerLog")),
-                Mockito.mock(KeyEventInteractor::class.java),
+                Mockito.mock(SysUIKeyEventHandler::class.java),
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index bcb060d..81382a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -60,7 +60,6 @@
 import dagger.Component
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
@@ -595,8 +594,7 @@
                         fromScene = SceneKey.Lockscreen,
                         toScene = key,
                         progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             sceneInteractor.setTransitionState(transitionState)
@@ -633,8 +631,7 @@
                         fromScene = key,
                         toScene = SceneKey.Lockscreen,
                         progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             sceneInteractor.setTransitionState(transitionState)
@@ -670,8 +667,7 @@
                         fromScene = SceneKey.Lockscreen,
                         toScene = SceneKey.Shade,
                         progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             sceneInteractor.setTransitionState(transitionState)
@@ -947,8 +943,7 @@
                         fromScene = SceneKey.Lockscreen,
                         toScene = key,
                         progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             sceneInteractor.setTransitionState(transitionState)
@@ -985,8 +980,7 @@
                         fromScene = SceneKey.Lockscreen,
                         toScene = key,
                         progress = progress,
-                        isInitiatedByUserInput = true,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = true,
                     )
                 )
             sceneInteractor.setTransitionState(transitionState)
@@ -1023,8 +1017,7 @@
                         fromScene = key,
                         toScene = SceneKey.Lockscreen,
                         progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             sceneInteractor.setTransitionState(transitionState)
@@ -1061,8 +1054,7 @@
                         fromScene = key,
                         toScene = SceneKey.Lockscreen,
                         progress = progress,
-                        isInitiatedByUserInput = true,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = true,
                     )
                 )
             sceneInteractor.setTransitionState(transitionState)
@@ -1097,9 +1089,8 @@
                     ObservableTransitionState.Transition(
                         fromScene = SceneKey.Lockscreen,
                         toScene = SceneKey.QuickSettings,
-                        progress = MutableStateFlow(0f),
-                        isInitiatedByUserInput = true,
-                        isUserInputOngoing = flowOf(false),
+                        progress = progress,
+                        isUserInputDriven = true,
                     )
                 )
             sceneInteractor.setTransitionState(transitionState)
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 bb20d94..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
@@ -17,7 +19,6 @@
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -32,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(
@@ -44,6 +46,7 @@
                     FakeConnectivityRepository(),
                 ),
             constants = mock(),
+            flags,
             scope = testScope.backgroundScope,
         )
 
@@ -85,8 +88,7 @@
                         fromScene = SceneKey.Shade,
                         toScene = SceneKey.QuickSettings,
                         progress = MutableStateFlow(0.5f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             )
@@ -104,8 +106,7 @@
                         fromScene = SceneKey.QuickSettings,
                         toScene = SceneKey.Shade,
                         progress = MutableStateFlow(0.5f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             )
@@ -123,8 +124,7 @@
                         fromScene = SceneKey.Gone,
                         toScene = SceneKey.Shade,
                         progress = MutableStateFlow(0.5f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
+                        isUserInputDriven = false,
                     )
                 )
             )
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/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
new file mode 100644
index 0000000..2f8f3bb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.SysUITestModule
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.mockito.Mockito.verify
+
+@SmallTest
+class NotificationsKeyguardViewStateRepositoryTest : SysuiTestCase() {
+
+    private val testComponent: TestComponent =
+        DaggerNotificationsKeyguardViewStateRepositoryTest_TestComponent.factory()
+            .create(test = this)
+
+    @Test
+    fun areNotifsFullyHidden_reflectsWakeUpCoordinator() =
+        with(testComponent) {
+            testScope.runTest {
+                whenever(mockWakeUpCoordinator.notificationsFullyHidden).thenReturn(false)
+                val notifsFullyHidden by collectLastValue(underTest.areNotificationsFullyHidden)
+                runCurrent()
+
+                assertThat(notifsFullyHidden).isFalse()
+
+                withArgCaptor { verify(mockWakeUpCoordinator).addListener(capture()) }
+                    .onFullyHiddenChanged(true)
+                runCurrent()
+
+                assertThat(notifsFullyHidden).isTrue()
+            }
+        }
+
+    @Test
+    fun isPulseExpanding_reflectsWakeUpCoordinator() =
+        with(testComponent) {
+            testScope.runTest {
+                whenever(mockWakeUpCoordinator.isPulseExpanding()).thenReturn(false)
+                val isPulseExpanding by collectLastValue(underTest.isPulseExpanding)
+                runCurrent()
+
+                assertThat(isPulseExpanding).isFalse()
+
+                withArgCaptor { verify(mockWakeUpCoordinator).addListener(capture()) }
+                    .onPulseExpansionChanged(true)
+                runCurrent()
+
+                assertThat(isPulseExpanding).isTrue()
+            }
+        }
+
+    @SysUISingleton
+    @Component(
+        modules =
+            [
+                SysUITestModule::class,
+            ]
+    )
+    interface TestComponent {
+
+        val underTest: NotificationsKeyguardViewStateRepositoryImpl
+
+        val mockWakeUpCoordinator: NotificationWakeUpCoordinator
+        val testScope: TestScope
+
+        @Component.Factory
+        interface Factory {
+            fun create(
+                @BindsInstance test: SysuiTestCase,
+            ): TestComponent
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
new file mode 100644
index 0000000..705a5a3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.SysUITestModule
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+@SmallTest
+class NotificationsKeyguardInteractorTest : SysuiTestCase() {
+
+    private val testComponent: TestComponent =
+        DaggerNotificationsKeyguardInteractorTest_TestComponent.factory().create(test = this)
+
+    @Test
+    fun areNotifsFullyHidden_reflectsRepository() =
+        with(testComponent) {
+            testScope.runTest {
+                repository.setNotificationsFullyHidden(false)
+                val notifsFullyHidden by collectLastValue(underTest.areNotificationsFullyHidden)
+                runCurrent()
+
+                assertThat(notifsFullyHidden).isFalse()
+
+                repository.setNotificationsFullyHidden(true)
+                runCurrent()
+
+                assertThat(notifsFullyHidden).isTrue()
+            }
+        }
+
+    @Test
+    fun isPulseExpanding_reflectsRepository() =
+        with(testComponent) {
+            testScope.runTest {
+                repository.setPulseExpanding(false)
+                val isPulseExpanding by collectLastValue(underTest.isPulseExpanding)
+                runCurrent()
+
+                assertThat(isPulseExpanding).isFalse()
+
+                repository.setPulseExpanding(true)
+                runCurrent()
+
+                assertThat(isPulseExpanding).isTrue()
+            }
+        }
+
+    @SysUISingleton
+    @Component(
+        modules =
+            [
+                SysUITestModule::class,
+            ]
+    )
+    interface TestComponent {
+
+        val underTest: NotificationsKeyguardInteractor
+
+        val repository: FakeNotificationsKeyguardViewStateRepository
+        val testScope: TestScope
+
+        @Component.Factory
+        interface Factory {
+            fun create(@BindsInstance test: SysuiTestCase): TestComponent
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index b120c47..f72142f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -11,10 +11,10 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.row;
+package com.android.systemui.statusbar.notification.footer.ui.view;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -31,8 +31,8 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
index 7caa5ccc..e57986d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
@@ -26,9 +26,7 @@
 import com.android.systemui.flags.FakeFeatureFlagsClassicModule
 import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.NotificationIconContainer
 import com.android.systemui.user.domain.UserDomainLayerModule
-import com.android.systemui.util.mockito.whenever
 import dagger.BindsInstance
 import dagger.Component
 import org.junit.Assert.assertFalse
@@ -37,7 +35,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -46,7 +43,6 @@
 class NotificationIconAreaControllerViewBinderWrapperImplTest : SysuiTestCase() {
 
     @Mock private lateinit var dozeParams: DozeParameters
-    @Mock private lateinit var aodIcons: NotificationIconContainer
 
     private lateinit var testComponent: TestComponent
     private val underTest
@@ -85,15 +81,6 @@
         assertTrue(underTest.shouldShowLowPriorityIcons())
     }
 
-    @Test
-    fun testAppearResetsTranslation() {
-        underTest.setupAodIcons(aodIcons)
-        whenever(dozeParams.shouldControlScreenOff()).thenReturn(false)
-        underTest.appearAodIcons()
-        verify(aodIcons).translationY = 0f
-        verify(aodIcons).alpha = 1.0f
-    }
-
     @SysUISingleton
     @Component(
         modules =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index 99c3b19..31efebb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
 import com.android.systemui.flags.FakeFeatureFlagsClassicModule
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -37,10 +38,13 @@
 import com.android.systemui.power.data.repository.FakePowerRepository
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
 import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
 import com.android.systemui.user.domain.UserDomainLayerModule
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.ui.AnimatedValue
 import com.google.common.truth.Truth.assertThat
 import dagger.BindsInstance
 import dagger.Component
@@ -59,19 +63,24 @@
 class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
 
     @Mock private lateinit var dozeParams: DozeParameters
+    @Mock private lateinit var screenOffAnimController: ScreenOffAnimationController
 
     private lateinit var testComponent: TestComponent
-    private val underTest
+    private val underTest: NotificationIconContainerAlwaysOnDisplayViewModel
         get() = testComponent.underTest
-    private val deviceProvisioningRepository
+    private val deviceEntryRepository: FakeDeviceEntryRepository
+        get() = testComponent.deviceEntryRepository
+    private val deviceProvisioningRepository: FakeDeviceProvisioningRepository
         get() = testComponent.deviceProvisioningRepository
-    private val keyguardRepository
+    private val keyguardRepository: FakeKeyguardRepository
         get() = testComponent.keyguardRepository
-    private val keyguardTransitionRepository
+    private val keyguardTransitionRepository: FakeKeyguardTransitionRepository
         get() = testComponent.keyguardTransitionRepository
-    private val powerRepository
+    private val notifsKeyguardRepository: FakeNotificationsKeyguardViewStateRepository
+        get() = testComponent.notifsKeyguardRepository
+    private val powerRepository: FakePowerRepository
         get() = testComponent.powerRepository
-    private val scope
+    private val scope: TestScope
         get() = testComponent.scope
 
     @Before
@@ -84,12 +93,14 @@
                     test = this,
                     featureFlags =
                         FakeFeatureFlagsClassicModule {
-                            set(Flags.FACE_AUTH_REFACTOR, value = false)
+                            setDefault(Flags.FACE_AUTH_REFACTOR)
                             set(Flags.FULL_SCREEN_USER_SWITCHER, value = false)
+                            setDefault(Flags.NEW_AOD_TRANSITION)
                         },
                     mocks =
                         TestMocksModule(
                             dozeParameters = dozeParams,
+                            screenOffAnimationController = screenOffAnimController,
                         ),
                 )
 
@@ -251,6 +262,204 @@
             assertThat(animationsEnabled).isFalse()
         }
 
+    @Test
+    fun isDozing_startAodTransition() =
+        scope.runTest {
+            val isDozing by collectLastValue(underTest.isDozing)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+            runCurrent()
+            assertThat(isDozing).isEqualTo(AnimatedValue(true, isAnimating = true))
+        }
+
+    @Test
+    fun isDozing_startDozeTransition() =
+        scope.runTest {
+            val isDozing by collectLastValue(underTest.isDozing)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.DOZING,
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+            runCurrent()
+            assertThat(isDozing).isEqualTo(AnimatedValue(true, isAnimating = false))
+        }
+
+    @Test
+    fun isDozing_startDozeToAodTransition() =
+        scope.runTest {
+            val isDozing by collectLastValue(underTest.isDozing)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.DOZING,
+                    to = KeyguardState.AOD,
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+            runCurrent()
+            assertThat(isDozing).isEqualTo(AnimatedValue(true, isAnimating = true))
+        }
+
+    @Test
+    fun isNotDozing_startAodToGoneTransition() =
+        scope.runTest {
+            val isDozing by collectLastValue(underTest.isDozing)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.GONE,
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+            runCurrent()
+            assertThat(isDozing).isEqualTo(AnimatedValue(false, isAnimating = true))
+        }
+
+    @Test
+    fun isDozing_stopAnimation() =
+        scope.runTest {
+            val isDozing by collectLastValue(underTest.isDozing)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.GONE,
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+            runCurrent()
+
+            underTest.completeDozeAnimation()
+            runCurrent()
+
+            assertThat(isDozing?.isAnimating).isEqualTo(false)
+        }
+
+    @Test
+    fun isNotVisible_pulseExpanding() =
+        scope.runTest {
+            val isVisible by collectLastValue(underTest.isVisible)
+            notifsKeyguardRepository.setPulseExpanding(true)
+            runCurrent()
+
+            assertThat(isVisible?.value).isFalse()
+        }
+
+    @Test
+    fun isNotVisible_notOnKeyguard_dontShowAodIconsWhenShade() =
+        scope.runTest {
+            val isVisible by collectLastValue(underTest.isVisible)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    to = KeyguardState.GONE,
+                    transitionState = TransitionState.FINISHED,
+                )
+            )
+            whenever(screenOffAnimController.shouldShowAodIconsWhenShade()).thenReturn(false)
+            runCurrent()
+
+            assertThat(isVisible).isEqualTo(AnimatedValue(false, isAnimating = false))
+        }
+
+    @Test
+    fun isVisible_bypassEnabled() =
+        scope.runTest {
+            val isVisible by collectLastValue(underTest.isVisible)
+            deviceEntryRepository.setBypassEnabled(true)
+            runCurrent()
+
+            assertThat(isVisible?.value).isTrue()
+        }
+
+    @Test
+    fun isNotVisible_pulseExpanding_notBypassing() =
+        scope.runTest {
+            val isVisible by collectLastValue(underTest.isVisible)
+            notifsKeyguardRepository.setPulseExpanding(true)
+            deviceEntryRepository.setBypassEnabled(false)
+            runCurrent()
+
+            assertThat(isVisible?.value).isEqualTo(false)
+        }
+
+    @Test
+    fun isVisible_notifsFullyHidden_bypassEnabled() =
+        scope.runTest {
+            val isVisible by collectLastValue(underTest.isVisible)
+            runCurrent()
+            notifsKeyguardRepository.setPulseExpanding(false)
+            deviceEntryRepository.setBypassEnabled(true)
+            notifsKeyguardRepository.setNotificationsFullyHidden(true)
+            runCurrent()
+
+            assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = true))
+        }
+
+    @Test
+    fun isVisible_notifsFullyHidden_bypassDisabled_aodDisabled() =
+        scope.runTest {
+            val isVisible by collectLastValue(underTest.isVisible)
+            notifsKeyguardRepository.setPulseExpanding(false)
+            deviceEntryRepository.setBypassEnabled(false)
+            whenever(dozeParams.alwaysOn).thenReturn(false)
+            notifsKeyguardRepository.setNotificationsFullyHidden(true)
+            runCurrent()
+
+            assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = false))
+        }
+
+    @Test
+    fun isVisible_notifsFullyHidden_bypassDisabled_displayNeedsBlanking() =
+        scope.runTest {
+            val isVisible by collectLastValue(underTest.isVisible)
+            notifsKeyguardRepository.setPulseExpanding(false)
+            deviceEntryRepository.setBypassEnabled(false)
+            whenever(dozeParams.alwaysOn).thenReturn(true)
+            whenever(dozeParams.displayNeedsBlanking).thenReturn(true)
+            notifsKeyguardRepository.setNotificationsFullyHidden(true)
+            runCurrent()
+
+            assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = false))
+        }
+
+    @Test
+    fun isVisible_notifsFullyHidden_bypassDisabled() =
+        scope.runTest {
+            val isVisible by collectLastValue(underTest.isVisible)
+            runCurrent()
+            notifsKeyguardRepository.setPulseExpanding(false)
+            deviceEntryRepository.setBypassEnabled(false)
+            whenever(dozeParams.alwaysOn).thenReturn(true)
+            whenever(dozeParams.displayNeedsBlanking).thenReturn(false)
+            notifsKeyguardRepository.setNotificationsFullyHidden(true)
+            runCurrent()
+
+            assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = true))
+        }
+
+    @Test
+    fun isVisible_stopAnimation() =
+        scope.runTest {
+            val isVisible by collectLastValue(underTest.isVisible)
+            notifsKeyguardRepository.setPulseExpanding(false)
+            deviceEntryRepository.setBypassEnabled(false)
+            whenever(dozeParams.alwaysOn).thenReturn(true)
+            whenever(dozeParams.displayNeedsBlanking).thenReturn(false)
+            notifsKeyguardRepository.setNotificationsFullyHidden(true)
+            runCurrent()
+
+            underTest.completeVisibilityAnimation()
+            runCurrent()
+
+            assertThat(isVisible?.isAnimating).isEqualTo(false)
+        }
+
     @SysUISingleton
     @Component(
         modules =
@@ -264,9 +473,11 @@
 
         val underTest: NotificationIconContainerAlwaysOnDisplayViewModel
 
+        val deviceEntryRepository: FakeDeviceEntryRepository
         val deviceProvisioningRepository: FakeDeviceProvisioningRepository
         val keyguardRepository: FakeKeyguardRepository
         val keyguardTransitionRepository: FakeKeyguardTransitionRepository
+        val notifsKeyguardRepository: FakeNotificationsKeyguardViewStateRepository
         val powerRepository: FakePowerRepository
         val scope: TestScope
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index d1518f7..e1e7f92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -82,6 +82,7 @@
             DaggerNotificationIconContainerStatusBarViewModelTest_TestComponent.factory()
                 .create(
                     test = this,
+                    // Configurable bindings
                     featureFlags =
                         FakeFeatureFlagsClassicModule {
                             set(Flags.FACE_AUTH_REFACTOR, value = false)
@@ -248,6 +249,7 @@
         modules =
             [
                 SysUITestModule::class,
+                // Real impls
                 BiometricsDomainLayerModule::class,
                 UserDomainLayerModule::class,
             ]
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 8f39ee6..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;
@@ -62,11 +67,15 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.FakeGlobalSettings;
 import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.utils.os.FakeHandler;
 
+import dagger.BindsInstance;
+import dagger.Component;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -77,9 +86,6 @@
 import java.util.Map;
 import java.util.function.Consumer;
 
-import dagger.BindsInstance;
-import dagger.Component;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -93,7 +99,9 @@
     @Mock private HighPriorityProvider mHighPriorityProvider;
     @Mock private SysuiStatusBarStateController mStatusBarStateController;
     @Mock private UserTracker mUserTracker;
-    private final FakeSettings mFakeSettings = new FakeSettings();
+    private final FakeSettings mSecureSettings = new FakeSettings();
+    private final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
+    private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
 
     private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
     private NotificationEntry mEntry;
@@ -113,8 +121,9 @@
                                 mHighPriorityProvider,
                                 mStatusBarStateController,
                                 mUserTracker,
-                                mFakeSettings,
-                                mFakeSettings);
+                                mSecureSettings,
+                                mGlobalSettings,
+                                mFeatureFlags);
         mKeyguardNotificationVisibilityProvider = component.getProvider();
         for (CoreStartable startable : component.getCoreStartables().values()) {
             startable.start();
@@ -223,7 +232,7 @@
         Consumer<String> listener = mock(Consumer.class);
         mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
 
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
 
         verify(listener).accept(anyString());
     }
@@ -234,7 +243,7 @@
         Consumer<String> listener = mock(Consumer.class);
         mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
 
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, true);
 
         verify(listener).accept(anyString());
     }
@@ -242,8 +251,8 @@
     @Test
     public void hideSilentNotificationsPerUserSettingWithHighPriorityParent() {
         when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
         GroupEntry parent = new GroupEntryBuilder()
                 .setKey("parent")
                 .addChild(mEntry)
@@ -264,8 +273,8 @@
     @Test
     public void keyguardShowing_hideSilentNotifications_perUserSetting() {
         when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
         mEntry = new NotificationEntryBuilder()
                 .setUser(new UserHandle(NOTIF_USER_ID))
                 .setImportance(IMPORTANCE_LOW)
@@ -277,8 +286,8 @@
     @Test
     public void keyguardShowing_hideSilentNotifications_perUserSetting_withHighPriorityParent() {
         when(mKeyguardStateController.isShowing()).thenReturn(true);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
         GroupEntry parent = new GroupEntryBuilder()
                 .setKey("parent")
                 .addChild(mEntry)
@@ -300,10 +309,10 @@
     public void hideSilentOnLockscreenSetting() {
         // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen
         setupUnfilteredState(mEntry);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
 
         // WHEN the show silent notifs on lockscreen setting is false
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
 
         // WHEN the notification is not high priority and not ambient
         mEntry = new NotificationEntryBuilder()
@@ -319,10 +328,10 @@
     public void showSilentOnLockscreenSetting() {
         // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen
         setupUnfilteredState(mEntry);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
 
         // WHEN the show silent notifs on lockscreen setting is true
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true);
 
         // WHEN the notification is not high priority and not ambient
         mEntry = new NotificationEntryBuilder()
@@ -338,7 +347,7 @@
     public void defaultSilentOnLockscreenSettingIsHide() {
         // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen
         setupUnfilteredState(mEntry);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
 
         // WHEN the notification is not high priority and not ambient
         mEntry = new NotificationEntryBuilder()
@@ -348,7 +357,8 @@
         when(mHighPriorityProvider.isExplicitlyHighPriority(any())).thenReturn(false);
 
         // WhHEN the show silent notifs on lockscreen setting is unset
-        assertNull(mFakeSettings.getString(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS));
+        assertNull(
+                mSecureSettings.getString(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS));
 
         assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
     }
@@ -359,7 +369,7 @@
         Consumer<String> listener = mock(Consumer.class);
         mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
 
-        mFakeSettings.putBool(Settings.Global.ZEN_MODE, true);
+        mGlobalSettings.putBool(Settings.Global.ZEN_MODE, true);
 
         verify(listener).accept(anyString());
     }
@@ -370,7 +380,7 @@
         Consumer<String> listener = mock(Consumer.class);
         mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
 
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true);
 
         verify(listener).accept(anyString());
     }
@@ -421,6 +431,7 @@
 
     @Test
     public void publicMode_settingsDisallow() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
         // GIVEN an 'unfiltered-keyguard-showing' state
         setupUnfilteredState(mEntry);
 
@@ -430,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);
 
@@ -470,8 +528,8 @@
     public void highPriorityCharacteristicsIgnored() {
         // GIVEN an 'unfiltered-keyguard-showing' state with silent notifications hidden
         setupUnfilteredState(mEntry);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
 
         // WHEN the notification doesn't exceed the threshold to show on the lockscreen, but does
         // have the "high priority characteristics" that would promote it to high priority
@@ -503,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()
@@ -557,7 +663,7 @@
                 .build());
 
         // WHEN its parent does exceed threshold tot show on the lockscreen
-        mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+        mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
         when(mHighPriorityProvider.isExplicitlyHighPriority(parent)).thenReturn(true);
 
         // THEN filter out the entry regardless of parent
@@ -632,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/notification/logging/StackStateLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt
deleted file mode 100644
index 47c5e5b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt
+++ /dev/null
@@ -1,77 +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.statusbar.notification.logging
-
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogcatEchoTracker
-import com.android.systemui.log.core.LogLevel
-import com.android.systemui.statusbar.notification.stack.StackStateLogger
-import com.google.common.truth.Truth
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class StackStateLoggerTest : SysuiTestCase() {
-    private val logBufferCounter = LogBufferCounter()
-    private lateinit var logger: StackStateLogger
-
-    @Before
-    fun setup() {
-        logger = StackStateLogger(logBufferCounter.logBuffer, logBufferCounter.logBuffer)
-    }
-
-    @Test
-    fun groupChildRemovalEvent() {
-        logger.groupChildRemovalEventProcessed(KEY)
-        verifyDidLog(1)
-        logger.groupChildRemovalAnimationEnded(KEY)
-        verifyDidLog(1)
-    }
-
-    class LogBufferCounter {
-        val recentLogs = mutableListOf<Pair<String, LogLevel>>()
-        val tracker =
-            object : LogcatEchoTracker {
-                override val logInBackgroundThread: Boolean = false
-                override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = false
-                override fun isTagLoggable(tagName: String, level: LogLevel): Boolean {
-                    recentLogs.add(tagName to level)
-                    return true
-                }
-            }
-        val logBuffer =
-            LogBuffer(name = "test", maxSize = 1, logcatEchoTracker = tracker, systrace = false)
-
-        fun verifyDidLog(times: Int) {
-            Truth.assertThat(recentLogs).hasSize(times)
-            recentLogs.clear()
-        }
-    }
-
-    private fun verifyDidLog(times: Int) {
-        logBufferCounter.verifyDidLog(times)
-    }
-
-    companion object {
-        private val KEY = "PACKAGE_NAME"
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt
index 8c3bfd5..f7632aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt
@@ -30,9 +30,12 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.TestUiOffloadThread
+import com.android.systemui.UiOffloadThread
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationTemplateViewWrapper.ActionPendingIntentCancellationHandler
+import com.android.systemui.util.leak.ReferenceTestUtils.waitForCondition
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -60,6 +63,12 @@
     fun setUp() {
         looper = TestableLooper.get(this)
         allowTestableLooperAsMainThread()
+        // Use main thread instead of UI offload thread to fix flakes.
+        mDependency.injectTestDependency(
+            UiOffloadThread::class.java,
+            TestUiOffloadThread(looper.looper)
+        )
+
         helper = NotificationTestHelper(mContext, mDependency, looper)
         row = helper.createRow()
         // Some code in the view iterates through parents so we need some extra containers around
@@ -88,12 +97,11 @@
         val action2 = createActionWithPendingIntent()
         val action3 = createActionWithPendingIntent()
         wrapper.onContentUpdated(row)
-        waitForUiOffloadThread() // Wait for cancellation registration to execute.
 
         val pi3 = getPendingIntent(action3)
         pi3.cancel()
-        looper.processAllMessages() // Wait for listener callbacks to execute
 
+        waitForActionDisabled(action3)
         assertThat(action1.isEnabled).isTrue()
         assertThat(action2.isEnabled).isTrue()
         assertThat(action3.isEnabled).isFalse()
@@ -109,12 +117,12 @@
         val wrapper = NotificationTemplateViewWrapper(mContext, view, row)
         val action = createActionWithPendingIntent()
         wrapper.onContentUpdated(row)
-        waitForUiOffloadThread() // Wait for cancellation registration to execute.
 
         // Cancel the intent and check action is now false.
         val pi = getPendingIntent(action)
         pi.cancel()
-        looper.processAllMessages() // Wait for listener callbacks to execute
+
+        waitForActionDisabled(action)
         assertThat(action.isEnabled).isFalse()
 
         // Create a NEW action and make sure that one will also be cancelled with same PI.
@@ -134,12 +142,13 @@
         val action2 = createActionWithPendingIntent()
         val action3 = createActionWithPendingIntent(getPendingIntent(action2))
         wrapper.onContentUpdated(row)
-        waitForUiOffloadThread() // Wait for cancellation registration to execute.
+        looper.processAllMessages()
 
         val pi = getPendingIntent(action2)
         pi.cancel()
-        looper.processAllMessages() // Wait for listener callbacks to execute
 
+        waitForActionDisabled(action2)
+        waitForActionDisabled(action3)
         assertThat(action1.isEnabled).isTrue()
         assertThat(action2.isEnabled).isFalse()
         assertThat(action3.isEnabled).isFalse()
@@ -152,10 +161,12 @@
         val action = createActionWithPendingIntent()
         wrapper.onContentUpdated(row)
         getPendingIntent(action).cancel()
-        ViewUtils.attachView(root)
-        waitForUiOffloadThread()
         looper.processAllMessages()
 
+        ViewUtils.attachView(root)
+        looper.processAllMessages()
+
+        waitForActionDisabled(action)
         assertThat(action.isEnabled).isFalse()
     }
 
@@ -173,7 +184,6 @@
         val wrapper = NotificationTemplateViewWrapper(mContext, view, row)
         wrapper.onContentUpdated(row)
         ViewUtils.detachView(root)
-        waitForUiOffloadThread()
         looper.processAllMessages()
 
         val captor = ArgumentCaptor.forClass(CancelListener::class.java)
@@ -194,7 +204,6 @@
         val action = createActionWithPendingIntent(spy)
         val wrapper = NotificationTemplateViewWrapper(mContext, view, row)
         wrapper.onContentUpdated(row)
-        waitForUiOffloadThread()
         looper.processAllMessages()
 
         // Grab set attach listener
@@ -213,7 +222,6 @@
             )
         action.setTagInternal(R.id.pending_intent_tag, newPi)
         wrapper.onContentUpdated(row)
-        waitForUiOffloadThread()
         looper.processAllMessages()
 
         // Listeners for original pending intent need to be cleaned up now.
@@ -251,4 +259,11 @@
         assertThat(pendingIntent).isNotNull()
         return pendingIntent
     }
+
+    private fun waitForActionDisabled(action: View) {
+        waitForCondition {
+            looper.processAllMessages()
+            !action.isEnabled
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 236bcb4..033c96a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -65,12 +65,12 @@
 
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.ExpandHelper;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.res.R;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.EmptyShadeView;
@@ -81,9 +81,9 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.FooterView;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -164,6 +164,7 @@
         mFeatureFlags.setDefault(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
         mFeatureFlags.setDefault(Flags.NOTIFICATION_SHELF_REFACTOR);
         mFeatureFlags.setDefault(Flags.NEW_AOD_TRANSITION);
+        mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX);
 
         // Inject dependencies before initializing the layout
         mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 93faa77..49906dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -5,18 +5,18 @@
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ShadeInterpolation.getContentAlpha
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.res.R
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.EmptyShadeView
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
+import com.android.systemui.statusbar.notification.footer.ui.view.FooterView.FooterViewState
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
-import com.android.systemui.statusbar.notification.row.FooterView
-import com.android.systemui.statusbar.notification.row.FooterView.FooterViewState
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Expect
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 416694b..1d8a346 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -50,15 +50,15 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dagger.NightDisplayListenerModule;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.AutoAddTracker;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.ReduceBrightColorsController;
-import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.UserSettingObserver;
 import com.android.systemui.qs.external.CustomTile;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
 import com.android.systemui.statusbar.policy.DataSaverController;
@@ -288,7 +288,7 @@
         inOrderSafety.verify(mSafetyController).removeCallback(any());
         inOrderSafety.verify(mSafetyController).addCallback(any());
 
-        SettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
+        UserSettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
         assertEquals(USER + 1, setting.getCurrentUser());
         assertTrue(setting.isListening());
     }
@@ -342,7 +342,7 @@
         inOrderSafety.verify(mSafetyController).removeCallback(any());
         inOrderSafety.verify(mSafetyController).addCallback(any());
 
-        SettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
+        UserSettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
         assertEquals(USER + 1, setting.getCurrentUser());
         assertFalse(setting.isListening());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index f18af61..c8cbe42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -18,6 +18,8 @@
 
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
+import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -1110,6 +1112,16 @@
         // THEN no NPE when fingerprintManager is null
     }
 
+    @Test
+    public void bubbleBarVisibility() {
+        createCentralSurfaces();
+        mCentralSurfaces.onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN);
+        verify(mBubbles).onStatusBarVisibilityChanged(false);
+
+        mCentralSurfaces.onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING);
+        verify(mBubbles).onStatusBarVisibilityChanged(true);
+    }
+
     /**
      * Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard}
      * to reconfigure the keyguard to reflect the requested showing/occluded states.
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/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index b36d09d..45e9224 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -19,8 +19,6 @@
 import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
 import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
 
-import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
-
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -37,6 +35,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+
 import android.service.trust.TrustAgentService;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -175,7 +175,6 @@
         mFeatureFlags = new FakeFeatureFlags();
         mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM, true);
         mFeatureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false);
-        mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, true);
         mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
         mFeatureFlags.set(Flags.ALTERNATE_BOUNCER_VIEW, false);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index e6f8c48..9aafee4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone
 
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import android.os.Handler
 import android.os.PowerManager
 import android.testing.AndroidTestingRunner
@@ -80,10 +82,14 @@
     @Mock
     private lateinit var handler: Handler
 
+    private lateinit var featureFlags: FakeFeatureFlags
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-
+        featureFlags = FakeFeatureFlags().apply {
+            set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false)
+        }
         controller = UnlockedScreenOffAnimationController(
                 context,
                 wakefulnessLifecycle,
@@ -95,7 +101,8 @@
                 dagger.Lazy<NotificationShadeWindowController> { notifShadeWindowController },
                 interactionJankMonitor,
                 powerManager,
-                handler = handler
+                handler = handler,
+                featureFlags,
         )
         controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
index d35ce76..8ecf6f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
@@ -18,12 +18,11 @@
 
 import android.os.Handler
 import android.os.Looper
-import android.os.UserHandle
 import android.provider.Settings.Global
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.FakeGlobalSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -48,15 +47,14 @@
     @Mock private lateinit var logger: TableLogBuffer
     private lateinit var bgHandler: Handler
     private lateinit var scope: CoroutineScope
-    private lateinit var settings: FakeSettings
+    private lateinit var settings: FakeGlobalSettings
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         bgHandler = Handler(Looper.getMainLooper())
         scope = CoroutineScope(IMMEDIATE)
-        settings = FakeSettings()
-        settings.userId = UserHandle.USER_ALL
+        settings = FakeGlobalSettings()
 
         underTest =
             AirplaneModeRepositoryImpl(
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/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index e761635..a1da167 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -75,6 +75,8 @@
     private BluetoothAdapter mMockAdapter;
     private List<CachedBluetoothDevice> mDevices;
 
+    private FakeExecutor mBackgroundExecutor;
+
     @Before
     public void setup() throws Exception {
         mTestableLooper = TestableLooper.get(this);
@@ -91,6 +93,7 @@
         when(mMockBluetoothManager.getProfileManager())
                 .thenReturn(mock(LocalBluetoothProfileManager.class));
         mMockDumpManager = mock(DumpManager.class);
+        mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());
 
         BluetoothRepository bluetoothRepository =
                 new FakeBluetoothRepository(mMockBluetoothManager);
@@ -101,6 +104,7 @@
                 mMockDumpManager,
                 mock(BluetoothLogger.class),
                 bluetoothRepository,
+                mBackgroundExecutor,
                 mTestableLooper.getLooper(),
                 mMockBluetoothManager,
                 mMockAdapter);
@@ -205,6 +209,7 @@
         mBluetoothControllerImpl.onAclConnectionStateChanged(device,
                 BluetoothProfile.STATE_CONNECTED);
         mBluetoothControllerImpl.onActiveDeviceChanged(device, BluetoothProfile.HEADSET);
+        mBackgroundExecutor.runAllReady();
 
         assertTrue(mBluetoothControllerImpl.isBluetoothAudioActive());
         assertTrue(mBluetoothControllerImpl.isBluetoothAudioProfileOnly());
@@ -290,6 +295,7 @@
                 BluetoothProfile.LE_AUDIO, /* isConnected= */ true, /* isActive= */ false);
 
         mBluetoothControllerImpl.onDeviceAdded(device);
+        mBackgroundExecutor.runAllReady();
 
         assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue();
     }
@@ -300,6 +306,7 @@
                 BluetoothProfile.HEADSET, /* isConnected= */ true, /* isActive= */ false);
 
         mBluetoothControllerImpl.onDeviceAdded(device);
+        mBackgroundExecutor.runAllReady();
 
         assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue();
     }
@@ -310,6 +317,7 @@
                 BluetoothProfile.A2DP, /* isConnected= */ true, /* isActive= */ false);
 
         mBluetoothControllerImpl.onDeviceAdded(device);
+        mBackgroundExecutor.runAllReady();
 
         assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue();
     }
@@ -320,6 +328,7 @@
                 BluetoothProfile.HEARING_AID, /* isConnected= */ true, /* isActive= */ false);
 
         mBluetoothControllerImpl.onDeviceAdded(device);
+        mBackgroundExecutor.runAllReady();
 
         assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue();
     }
@@ -337,6 +346,8 @@
         mBluetoothControllerImpl.onDeviceAdded(device2);
         mBluetoothControllerImpl.onDeviceAdded(device3);
 
+        mBackgroundExecutor.runAllReady();
+
         assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue();
     }
 
@@ -349,6 +360,7 @@
 
         mBluetoothControllerImpl.onDeviceAdded(device1);
         mBluetoothControllerImpl.onDeviceAdded(device2);
+        mBackgroundExecutor.runAllReady();
 
         assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isFalse();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
index 6094135..361fa5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeGlobalSettings
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.wrapper.BuildInfo
@@ -67,19 +68,22 @@
 
     private lateinit var mainExecutor: FakeExecutor
     private lateinit var testableLooper: TestableLooper
-    private lateinit var settings: FakeSettings
+    private lateinit var secureSettings: FakeSettings
+    private lateinit var globalSettings: FakeGlobalSettings
+
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         mainExecutor = FakeExecutor(FakeSystemClock())
-        settings = FakeSettings()
+        secureSettings = FakeSettings()
+        globalSettings = FakeGlobalSettings()
         `when`(userTracker.userId).thenReturn(START_USER)
         whenever(buildInfo.isDebuggable).thenReturn(false)
         controller = DeviceProvisionedControllerImpl(
-                settings,
-                settings,
+                secureSettings,
+                globalSettings,
                 userTracker,
                 dumpManager,
                 buildInfo,
@@ -108,7 +112,7 @@
 
     @Test
     fun testProvisionedWhenCreated() {
-        settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
+        globalSettings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
         init()
 
         assertThat(controller.isDeviceProvisioned).isTrue()
@@ -116,7 +120,7 @@
 
     @Test
     fun testFrpActiveWhenCreated() {
-        settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1)
+        globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1)
         init()
 
         assertThat(controller.isFrpActive).isTrue()
@@ -124,7 +128,7 @@
 
     @Test
     fun testUserSetupWhenCreated() {
-        settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
+        secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
         init()
 
         assertThat(controller.isUserSetup(START_USER))
@@ -134,7 +138,7 @@
     fun testDeviceProvisionedChange() {
         init()
 
-        settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
+        globalSettings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
         testableLooper.processAllMessages() // background observer
 
         assertThat(controller.isDeviceProvisioned).isTrue()
@@ -144,7 +148,7 @@
     fun testFrpActiveChange() {
         init()
 
-        settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1)
+        globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1)
         testableLooper.processAllMessages() // background observer
 
         assertThat(controller.isFrpActive).isTrue()
@@ -154,7 +158,7 @@
     fun testUserSetupChange() {
         init()
 
-        settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
+        secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
         testableLooper.processAllMessages() // background observer
 
         assertThat(controller.isUserSetup(START_USER)).isTrue()
@@ -165,7 +169,7 @@
         init()
         val otherUser = 10
 
-        settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser)
+        secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser)
         testableLooper.processAllMessages() // background observer
 
         assertThat(controller.isUserSetup(START_USER)).isFalse()
@@ -175,7 +179,7 @@
     @Test
     fun testCurrentUserSetup() {
         val otherUser = 10
-        settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser)
+        secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser)
         init()
 
         assertThat(controller.isCurrentUserSetup).isFalse()
@@ -219,7 +223,7 @@
         init()
         controller.addCallback(listener)
 
-        settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
+        secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
         testableLooper.processAllMessages()
         mainExecutor.runAllReady()
 
@@ -234,7 +238,7 @@
         init()
         controller.addCallback(listener)
 
-        settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
+        globalSettings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
         testableLooper.processAllMessages()
         mainExecutor.runAllReady()
 
@@ -249,7 +253,7 @@
         init()
         controller.addCallback(listener)
 
-        settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1)
+        globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1)
         testableLooper.processAllMessages()
         mainExecutor.runAllReady()
 
@@ -266,9 +270,9 @@
         controller.removeCallback(listener)
 
         switchUser(10)
-        settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
-        settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
-        settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1)
+        secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
+        globalSettings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
+        globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1)
 
         testableLooper.processAllMessages()
         mainExecutor.runAllReady()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
index 1250228..d33806e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy
 
+import com.android.systemui.flags.FakeFeatureFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.ViewUtils
@@ -77,11 +78,13 @@
     private lateinit var view: FrameLayout
     private lateinit var testableLooper: TestableLooper
     private lateinit var keyguardQsUserSwitchController: KeyguardQsUserSwitchController
+    private lateinit var featureFlags: FakeFeatureFlags
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
+        featureFlags = FakeFeatureFlags()
 
         view = LayoutInflater.from(context)
                 .inflate(R.layout.keyguard_qs_user_switch, null) as FrameLayout
@@ -98,6 +101,7 @@
                 dozeParameters,
                 screenOffAnimationController,
                 userSwitchDialogController,
+                featureFlags,
                 uiEventLogger)
 
         ViewUtils.attachView(view)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index 66c5aaa..6825f65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -38,7 +38,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.ZenModeController.Callback;
-import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.FakeGlobalSettings;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -67,7 +67,7 @@
     UserTracker mUserTracker;
     private ZenModeControllerImpl mController;
 
-    private final FakeSettings mGlobalSettings = new FakeSettings();
+    private final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
 
     @Before
     public void setUp() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index e249cec..0d78ae9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -29,7 +29,7 @@
 import com.android.systemui.user.data.model.SelectedUserModel
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.FakeGlobalSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -56,14 +56,14 @@
 
     private lateinit var underTest: UserRepositoryImpl
 
-    private lateinit var globalSettings: FakeSettings
+    private lateinit var globalSettings: FakeGlobalSettings
     private lateinit var tracker: FakeUserTracker
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        globalSettings = FakeSettings()
+        globalSettings = FakeGlobalSettings()
         tracker = FakeUserTracker()
     }
 
@@ -282,20 +282,17 @@
             com.android.internal.R.bool.config_expandLockScreenUserSwitcher,
             true,
         )
-        globalSettings.putIntForUser(
+        globalSettings.putInt(
             UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER,
             if (isSimpleUserSwitcher) 1 else 0,
-            UserHandle.USER_SYSTEM,
         )
-        globalSettings.putIntForUser(
+        globalSettings.putInt(
             Settings.Global.ADD_USERS_WHEN_LOCKED,
             if (isAddUsersFromLockscreen) 1 else 0,
-            UserHandle.USER_SYSTEM,
         )
-        globalSettings.putIntForUser(
+        globalSettings.putInt(
             Settings.Global.USER_SWITCHER_ENABLED,
             if (isUserSwitcherEnabled) 1 else 0,
-            UserHandle.USER_SYSTEM,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
new file mode 100644
index 0000000..aaf8d07
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.ui
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AnimatedValueTest : SysuiTestCase() {
+
+    @Test
+    fun animatableEvent_updatesValue() = runTest {
+        val events = MutableSharedFlow<AnimatableEvent<Int>>()
+        val values = events.toAnimatedValueFlow(completionEvents = emptyFlow())
+        val value by collectLastValue(values)
+        runCurrent()
+
+        events.emit(AnimatableEvent(value = 1, startAnimating = false))
+
+        assertThat(value).isEqualTo(AnimatedValue(value = 1, isAnimating = false))
+    }
+
+    @Test
+    fun animatableEvent_startAnimation() = runTest {
+        val events = MutableSharedFlow<AnimatableEvent<Int>>()
+        val values = events.toAnimatedValueFlow(completionEvents = emptyFlow())
+        val value by collectLastValue(values)
+        runCurrent()
+
+        events.emit(AnimatableEvent(value = 1, startAnimating = true))
+
+        assertThat(value).isEqualTo(AnimatedValue(value = 1, isAnimating = true))
+    }
+
+    @Test
+    fun animatableEvent_startAnimation_alreadyAnimating() = runTest {
+        val events = MutableSharedFlow<AnimatableEvent<Int>>()
+        val values = events.toAnimatedValueFlow(completionEvents = emptyFlow())
+        val value by collectLastValue(values)
+        runCurrent()
+
+        events.emit(AnimatableEvent(value = 1, startAnimating = true))
+        events.emit(AnimatableEvent(value = 2, startAnimating = true))
+
+        assertThat(value).isEqualTo(AnimatedValue(value = 2, isAnimating = true))
+    }
+
+    @Test
+    fun animatedValue_stopAnimating() = runTest {
+        val events = MutableSharedFlow<AnimatableEvent<Int>>()
+        val stopEvent = MutableSharedFlow<Unit>()
+        val values = events.toAnimatedValueFlow(completionEvents = stopEvent)
+        val value by collectLastValue(values)
+        runCurrent()
+
+        events.emit(AnimatableEvent(value = 1, startAnimating = true))
+        stopEvent.emit(Unit)
+
+        assertThat(value).isEqualTo(AnimatedValue(value = 1, isAnimating = false))
+    }
+
+    @Test
+    fun animatedValue_stopAnimating_notAnimating() = runTest {
+        val events = MutableSharedFlow<AnimatableEvent<Int>>()
+        val stopEvent = MutableSharedFlow<Unit>()
+        val values = events.toAnimatedValueFlow(completionEvents = stopEvent)
+        values.launchIn(backgroundScope)
+        runCurrent()
+
+        events.emit(AnimatableEvent(value = 1, startAnimating = false))
+
+        assertThat(stopEvent.subscriptionCount.value).isEqualTo(0)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 28fc5db..b8f747b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -42,6 +42,7 @@
 import android.content.res.Configuration;
 import android.media.AudioManager;
 import android.os.SystemClock;
+import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Log;
@@ -70,6 +71,10 @@
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.FakeConfigurationController;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.SecureSettings;
+
+import dagger.Lazy;
 
 import org.junit.After;
 import org.junit.Before;
@@ -122,6 +127,8 @@
     @Mock CsdWarningDialog mCsdWarningDialog;
     @Mock
     DevicePostureController mPostureController;
+    @Mock
+    private Lazy<SecureSettings> mLazySecureSettings;
 
     private final CsdWarningDialog.Factory mCsdWarningDialogFactory =
             new CsdWarningDialog.Factory() {
@@ -133,6 +140,7 @@
 
     private FakeFeatureFlags mFeatureFlags;
     private int mLongestHideShowAnimationDuration = 250;
+    private FakeSettings mSecureSettings;
 
     @Rule
     public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
@@ -162,6 +170,10 @@
 
         mFeatureFlags = new FakeFeatureFlags();
 
+        mSecureSettings = new FakeSettings();
+
+        when(mLazySecureSettings.get()).thenReturn(mSecureSettings);
+
         mDialog = new VolumeDialogImpl(
                 getContext(),
                 mVolumeDialogController,
@@ -177,7 +189,8 @@
                 mPostureController,
                 mTestableLooper.getLooper(),
                 mDumpManager,
-                mFeatureFlags);
+                mFeatureFlags,
+                mLazySecureSettings);
         mDialog.init(0, null);
         State state = createShellState();
         mDialog.onStateChangedH(state);
@@ -242,6 +255,17 @@
     }
 
     @Test
+    public void testSetTimeoutValue_ComputeTimeout() {
+        mSecureSettings.putInt(Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, 7000);
+        Mockito.reset(mAccessibilityMgr);
+        mDialog.init(0, null);
+        mDialog.rescheduleTimeoutH();
+        verify(mAccessibilityMgr).getRecommendedTimeoutMillis(
+                7000,
+                AccessibilityManager.FLAG_CONTENT_CONTROLS);
+    }
+
+    @Test
     public void testComputeTimeout_tooltip() {
         Mockito.reset(mAccessibilityMgr);
         mDialog.showCaptionsTooltip();
diff --git a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
index 41dbc14..b820ca6 100644
--- a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
+++ b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
@@ -68,13 +68,26 @@
 
     private final Object mLock = new Object();
     private final TestHandler mTestHandler = new TestHandler();
+    private final long mStartTime;
+    private long mTotalTimeDelta = 0;
+
     /**
-     * initializing the start time with {@link SystemClock#uptimeMillis()} reduces the discrepancies
-     * with various internals of classes like ValueAnimator which can sometimes read that clock via
+     * Construct an AnimatorTestRule with a custom start time.
+     * @see #AnimatorTestRule()
+     */
+    public AnimatorTestRule(long startTime) {
+        mStartTime = startTime;
+    }
+
+    /**
+     * Construct an AnimatorTestRule with a start time of {@link SystemClock#uptimeMillis()}.
+     * Initializing the start time with this clock reduces the discrepancies with various internals
+     * of classes like ValueAnimator which can sometimes read that clock via
      * {@link android.view.animation.AnimationUtils#currentAnimationTimeMillis()}.
      */
-    private final long mStartTime = SystemClock.uptimeMillis();
-    private long mTotalTimeDelta = 0;
+    public AnimatorTestRule() {
+        this(SystemClock.uptimeMillis());
+    }
 
     @NonNull
     @Override
diff --git a/packages/SystemUI/tests/utils/src/android/animation/PlatformAnimatorIsolationRule.kt b/packages/SystemUI/tests/utils/src/android/animation/PlatformAnimatorIsolationRule.kt
index 43a26f3..ca5e1d0 100644
--- a/packages/SystemUI/tests/utils/src/android/animation/PlatformAnimatorIsolationRule.kt
+++ b/packages/SystemUI/tests/utils/src/android/animation/PlatformAnimatorIsolationRule.kt
@@ -41,7 +41,7 @@
         private fun onError() =
             exceptionDeferrer.fail(
                 "Test's animations are not isolated! " +
-                    "Did you forget to add an AnimatorTestRule to your test class?"
+                    "Did you forget to add an AnimatorTestRule as a @Rule?"
             )
 
         fun throwDeferred() = exceptionDeferrer.throwDeferred()
diff --git a/packages/SystemUI/tests/utils/src/androidx/core/animation/AndroidXAnimatorIsolationRule.kt b/packages/SystemUI/tests/utils/src/androidx/core/animation/AndroidXAnimatorIsolationRule.kt
index 7a97029..95335a6 100644
--- a/packages/SystemUI/tests/utils/src/androidx/core/animation/AndroidXAnimatorIsolationRule.kt
+++ b/packages/SystemUI/tests/utils/src/androidx/core/animation/AndroidXAnimatorIsolationRule.kt
@@ -37,7 +37,7 @@
         private fun onError() =
             exceptionDeferrer.fail(
                 "Test's animations are not isolated! " +
-                    "Did you forget to add an AnimatorTestRule to your test class?"
+                    "Did you forget to add an AnimatorTestRule as a @Rule?"
             )
 
         fun throwDeferred() = exceptionDeferrer.throwDeferred()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
index 813197b1..dc5fd95 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.policy.FakeConfigurationControllerModule
 import com.android.systemui.statusbar.policy.FakeSplitShadeStateControllerModule
 import com.android.systemui.util.concurrency.FakeExecutorModule
+import com.android.systemui.util.time.FakeSystemClockModule
 import dagger.Module
 
 @Module(
@@ -36,6 +37,7 @@
             FakeSceneModule::class,
             FakeSettingsModule::class,
             FakeSplitShadeStateControllerModule::class,
+            FakeSystemClockModule::class,
             FakeSystemUiDataLayerModule::class,
             FakeUiEventLoggerModule::class,
         ]
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index d6632a3..6ef812b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -15,7 +15,7 @@
  */
 package com.android.systemui;
 
-import static com.android.systemui.animation.FakeDialogLaunchAnimatorKt.fakeDialogLaunchAnimator;
+import static com.android.systemui.Flags.FLAG_EXAMPLE_FLAG;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
@@ -27,6 +27,7 @@
 import android.os.Looper;
 import android.os.MessageQueue;
 import android.os.ParcelFileDescriptor;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.DexmakerShareClassLoaderRule;
 import android.testing.LeakCheck;
 import android.testing.TestWithLooperRule;
@@ -37,19 +38,15 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.UiDevice;
 
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.broadcast.FakeBroadcastDispatcher;
 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
-import org.junit.ClassRule;
 import org.junit.Rule;
 import org.mockito.Mockito;
 
@@ -69,11 +66,14 @@
     private Handler mHandler;
 
     // set the lowest order so it's the outermost rule
-    @ClassRule(order = Integer.MIN_VALUE)
-    public static AndroidXAnimatorIsolationRule mAndroidXAnimatorIsolationRule =
+    @Rule(order = Integer.MIN_VALUE)
+    public AndroidXAnimatorIsolationRule mAndroidXAnimatorIsolationRule =
             new AndroidXAnimatorIsolationRule();
 
     @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Rule
     public SysuiTestableContext mContext = new SysuiTestableContext(
             InstrumentationRegistry.getContext(), getLeakCheck());
     @Rule
@@ -94,10 +94,11 @@
         if (isRobolectricTest()) {
             mContext = mContext.createDefaultDisplayContext();
         }
-        SystemUIInitializer initializer = new SystemUIInitializerImpl(mContext);
-        initializer.init(true);
-        mDependency = new TestableDependency(initializer.getSysUIComponent().createDependency());
-        Dependency.setInstance(mDependency);
+        // Set the value of a single gantry flag inside the com.android.systemui package to
+        // ensure all flags in that package are faked (and thus require a value to be set).
+        mSetFlagsRule.disableFlags(FLAG_EXAMPLE_FLAG);
+
+        mDependency = SysuiTestDependencyKt.installSysuiTestDependency(mContext);
         mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(
                 mContext,
                 mContext.getMainExecutor(),
@@ -124,13 +125,6 @@
         // reference and are never sent to the Context. This will also prevent a real
         // BroadcastDispatcher from actually registering receivers.
         mDependency.injectTestDependency(BroadcastDispatcher.class, mFakeBroadcastDispatcher);
-        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
-
-        // Make sure that all tests on any SystemUIDialog does not crash because this dependency
-        // is missing (constructing the actual one would throw).
-        // TODO(b/219008720): Remove this.
-        mDependency.injectMockDependency(SystemUIDialogManager.class);
-        mDependency.injectTestDependency(DialogLaunchAnimator.class, fakeDialogLaunchAnimator());
     }
 
     protected boolean shouldFailOnLeakedReceiver() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt
new file mode 100644
index 0000000..c791f4f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt
@@ -0,0 +1,26 @@
+package com.android.systemui
+
+import android.annotation.SuppressLint
+import android.content.Context
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.fakeDialogLaunchAnimator
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+
+@SuppressLint("VisibleForTests")
+fun installSysuiTestDependency(context: Context): TestableDependency {
+    val initializer: SystemUIInitializer = SystemUIInitializerImpl(context)
+    initializer.init(true)
+
+    val dependency = TestableDependency(initializer.sysUIComponent.createDependency())
+    Dependency.setInstance(dependency)
+
+    dependency.injectMockDependency(KeyguardUpdateMonitor::class.java)
+
+    // Make sure that all tests on any SystemUIDialog does not crash because this dependency
+    // is missing (constructing the actual one would throw).
+    // TODO(b/219008720): Remove this.
+    dependency.injectMockDependency(SystemUIDialogManager::class.java)
+    dependency.injectTestDependency(DialogLaunchAnimator::class.java, fakeDialogLaunchAnimator())
+    return dependency
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java b/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java
new file mode 100644
index 0000000..fdd26eb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+
+/**
+ * UiOffloadThread that can be used for testing as part of {@link TestableDependency}.
+ */
+public class TestUiOffloadThread extends UiOffloadThread {
+    private final Handler mTestHandler;
+
+    public TestUiOffloadThread(Looper looper) {
+        mTestHandler = new Handler(looper);
+    }
+
+    @Override
+    public Future<?> execute(Runnable runnable) {
+        Looper myLooper = Looper.myLooper();
+        if (myLooper != null && myLooper.isCurrentThread()) {
+            try {
+                runnable.run();
+                return CompletableFuture.completedFuture(null);
+            } catch (Exception e) {
+                return CompletableFuture.failedFuture(e);
+            }
+        }
+
+        final CompletableFuture<?> future = new CompletableFuture<>();
+        mTestHandler.post(() -> {
+            try {
+                runnable.run();
+                future.complete(null);
+            } catch (Exception e) {
+                future.completeExceptionally(e);
+            }
+        });
+
+        return future;
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt
index 0ced19e..ba9c5ed 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt
@@ -28,11 +28,21 @@
  * advanced together.
  */
 class AnimatorTestRule : TestRule {
+    // Create the androidx rule, which initializes start time to SystemClock.uptimeMillis(),
+    // then copy that time to the platform rule so that the two clocks are in sync.
     private val androidxRule = androidx.core.animation.AnimatorTestRule()
-    private val platformRule = android.animation.AnimatorTestRule()
+    private val platformRule = android.animation.AnimatorTestRule(androidxRule.startTime)
     private val advanceAndroidXTimeBy =
         Consumer<Long> { timeDelta -> androidxRule.advanceTimeBy(timeDelta) }
 
+    /** Access the mStartTime field; bypassing the restriction of being on a looper thread. */
+    private val androidx.core.animation.AnimatorTestRule.startTime: Long
+        get() =
+            javaClass.getDeclaredField("mStartTime").let { field ->
+                field.isAccessible = true
+                field.getLong(this)
+            }
+
     /**
      * Chain is for simplicity not to force a particular order; order should not matter, because
      * each rule affects a different AnimationHandler classes, and no callbacks to code under test
@@ -55,4 +65,11 @@
         //  animation from one to start later than the other.
         platformRule.advanceTimeBy(timeDelta, advanceAndroidXTimeBy)
     }
+
+    /**
+     * Returns the current time in milliseconds tracked by the AnimationHandlers. Note that this is
+     * a different time than the time tracked by {@link SystemClock}.
+     */
+    val currentTime: Long
+        get() = androidxRule.currentTime
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/FakeAuthenticationDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/FakeAuthenticationDataLayerModule.kt
new file mode 100644
index 0000000..8fa6695
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/FakeAuthenticationDataLayerModule.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.authentication.data
+
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepositoryModule
+import dagger.Module
+
+@Module(includes = [FakeAuthenticationRepositoryModule::class])
+object FakeAuthenticationDataLayerModule
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index 4fc3e3f..ddfe79a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -24,10 +24,17 @@
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.authentication.shared.model.AuthenticationResultModel
 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.currentTime
 
 class FakeAuthenticationRepository(
     private val deviceEntryRepository: FakeDeviceEntryRepository,
@@ -201,3 +208,19 @@
         }
     }
 }
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@Module(includes = [FakeAuthenticationRepositoryModule.Bindings::class])
+object FakeAuthenticationRepositoryModule {
+    @Provides
+    @SysUISingleton
+    fun provideFake(
+        deviceEntryRepository: FakeDeviceEntryRepository,
+        scope: TestScope,
+    ) = FakeAuthenticationRepository(deviceEntryRepository, currentTime = { scope.currentTime })
+
+    @Module
+    interface Bindings {
+        @Binds fun bindFake(fake: FakeAuthenticationRepository): AuthenticationRepository
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
index 10b284a..5dcc742 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -47,7 +47,7 @@
     }
 
     override fun getDimensionPixelSize(id: Int): Int {
-        throw IllegalStateException("Don't use for tests")
+        return 0
     }
 }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalTutorialRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalTutorialRepository.kt
new file mode 100644
index 0000000..902e852
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalTutorialRepository.kt
@@ -0,0 +1,19 @@
+package com.android.systemui.communal.data.repository
+
+import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
+import android.provider.Settings.Secure.HubModeTutorialState
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Fake implementation of [CommunalTutorialRepository] */
+class FakeCommunalTutorialRepository() : CommunalTutorialRepository {
+    private val _tutorialSettingState = MutableStateFlow(HUB_MODE_TUTORIAL_NOT_STARTED)
+    override val tutorialSettingState: StateFlow<Int> = _tutorialSettingState
+    override suspend fun setTutorialState(@HubModeTutorialState state: Int) {
+        setTutorialSettingState(state)
+    }
+
+    fun setTutorialSettingState(@HubModeTutorialState state: Int) {
+        _tutorialSettingState.value = state
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt
index 29fb52a..cffbf02 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.data
 
+import com.android.systemui.authentication.data.FakeAuthenticationDataLayerModule
 import com.android.systemui.bouncer.data.repository.FakeBouncerDataLayerModule
 import com.android.systemui.common.ui.data.FakeCommonDataLayerModule
 import com.android.systemui.deviceentry.data.FakeDeviceEntryDataLayerModule
@@ -29,6 +30,7 @@
 @Module(
     includes =
         [
+            FakeAuthenticationDataLayerModule::class,
             FakeBouncerDataLayerModule::class,
             FakeCommonDataLayerModule::class,
             FakeDeviceEntryDataLayerModule::class,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryDataLayerModule.kt
new file mode 100644
index 0000000..f4feee1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryDataLayerModule.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.deviceentry.data.repository
+
+import dagger.Module
+
+@Module(includes = [FakeDeviceEntryRepositoryModule::class]) object FakeDeviceEntryDataLayerModule
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index 26d95c0..f029348 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package com.android.systemui.deviceentry.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
@@ -13,15 +28,13 @@
 class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository {
 
     private var isInsecureLockscreenEnabled = true
-    private var isBypassEnabled = false
+
+    private val _isBypassEnabled = MutableStateFlow(false)
+    override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled
 
     private val _isUnlocked = MutableStateFlow(false)
     override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
 
-    override fun isBypassEnabled(): Boolean {
-        return isBypassEnabled
-    }
-
     override suspend fun isInsecureLockscreenEnabled(): Boolean {
         return isInsecureLockscreenEnabled
     }
@@ -35,7 +48,7 @@
     }
 
     fun setBypassEnabled(isBypassEnabled: Boolean) {
-        this.isBypassEnabled = isBypassEnabled
+        _isBypassEnabled.value = isBypassEnabled
     }
 }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt
new file mode 100644
index 0000000..95b5316
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyevent.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeKeyEventRepository() : KeyEventRepository {
+    private val _isPowerButtonDown = MutableStateFlow(false)
+    override val isPowerButtonDown: Flow<Boolean> = _isPowerButtonDown.asStateFlow()
+
+    fun setPowerButtonDown(isDown: Boolean) {
+        _isPowerButtonDown.value = isDown
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
index bf77b1a..911eafa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -53,7 +53,7 @@
 import com.android.systemui.user.data.repository.UserSwitcherRepositoryImpl
 import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.FakeGlobalSettings
 import com.android.systemui.util.settings.GlobalSettings
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -69,8 +69,8 @@
     private val scheduler: TestCoroutineScheduler,
 ) {
     /** Enable or disable the user switcher in the settings. */
-    fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean, userId: Int) {
-        settings.putBoolForUser(Settings.Global.USER_SWITCHER_ENABLED, enabled, userId)
+    fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean) {
+        settings.putBool(Settings.Global.USER_SWITCHER_ENABLED, enabled)
 
         // The settings listener is processing messages on the bgHandler (usually backed by a
         // testableLooper in tests), so let's make sure we process the callback before continuing.
@@ -152,7 +152,7 @@
         userTracker: UserTracker = FakeUserTracker(),
         userSwitcherController: UserSwitcherController = mock(),
         userInfoController: UserInfoController = FakeUserInfoController(),
-        settings: GlobalSettings = FakeSettings(),
+        settings: GlobalSettings = FakeGlobalSettings(),
     ): UserSwitcherRepository {
         return UserSwitcherRepositoryImpl(
             context,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt
index 822edfc7..e59f642 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.statusbar.data
 
 import com.android.systemui.statusbar.disableflags.data.FakeStatusBarDisableFlagsDataLayerModule
+import com.android.systemui.statusbar.notification.data.FakeStatusBarNotificationsDataLayerModule
 import com.android.systemui.statusbar.pipeline.data.FakeStatusBarPipelineDataLayerModule
 import com.android.systemui.statusbar.policy.data.FakeStatusBarPolicyDataLayerModule
 import dagger.Module
@@ -24,6 +25,7 @@
     includes =
         [
             FakeStatusBarDisableFlagsDataLayerModule::class,
+            FakeStatusBarNotificationsDataLayerModule::class,
             FakeStatusBarPipelineDataLayerModule::class,
             FakeStatusBarPolicyDataLayerModule::class,
         ]
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt
new file mode 100644
index 0000000..788e3aa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.data
+
+import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardStateRepositoryModule
+import dagger.Module
+
+@Module(includes = [FakeNotificationsKeyguardStateRepositoryModule::class])
+object FakeStatusBarNotificationsDataLayerModule
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeNotificationsKeyguardViewStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeNotificationsKeyguardViewStateRepository.kt
new file mode 100644
index 0000000..5d3cb4d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeNotificationsKeyguardViewStateRepository.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+@SysUISingleton
+class FakeNotificationsKeyguardViewStateRepository @Inject constructor() :
+    NotificationsKeyguardViewStateRepository {
+    private val _notificationsFullyHidden = MutableStateFlow(false)
+    override val areNotificationsFullyHidden: Flow<Boolean> = _notificationsFullyHidden
+
+    private val _isPulseExpanding = MutableStateFlow(false)
+    override val isPulseExpanding: Flow<Boolean> = _isPulseExpanding
+
+    fun setNotificationsFullyHidden(fullyHidden: Boolean) {
+        _notificationsFullyHidden.value = fullyHidden
+    }
+
+    fun setPulseExpanding(expanding: Boolean) {
+        _isPulseExpanding.value = expanding
+    }
+}
+
+@Module
+interface FakeNotificationsKeyguardStateRepositoryModule {
+    @Binds
+    fun bindFake(
+        fake: FakeNotificationsKeyguardViewStateRepository
+    ): NotificationsKeyguardViewStateRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt
index 5de05c2..1f48d94 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.util.concurrency
 
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.util.time.FakeSystemClock
 import dagger.Binds
@@ -22,14 +23,12 @@
 import dagger.Provides
 import java.util.concurrent.Executor
 
-@Module(includes = [FakeExecutorModule.Bindings::class])
-class FakeExecutorModule(
-    @get:Provides val clock: FakeSystemClock = FakeSystemClock(),
-) {
-    @get:Provides val executor = FakeExecutor(clock)
+@Module
+interface FakeExecutorModule {
+    @Binds @Main @SysUISingleton fun bindMainExecutor(executor: FakeExecutor): Executor
 
-    @Module
-    interface Bindings {
-        @Binds @Main fun bindMainExecutor(executor: FakeExecutor): Executor
+    companion object {
+        @Provides
+        fun provideFake(clock: FakeSystemClock) = FakeExecutor(clock)
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
new file mode 100644
index 0000000..db5eaff
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class FakeGlobalSettings implements GlobalSettings {
+    private final Map<String, String> mValues = new HashMap<>();
+    private final Map<String, List<ContentObserver>> mContentObserversAllUsers = new HashMap<>();
+
+    public static final Uri CONTENT_URI = Uri.parse("content://settings/fake_global");
+
+    public FakeGlobalSettings() {
+    }
+
+    @Override
+    public ContentResolver getContentResolver() {
+        return null;
+    }
+
+    @Override
+    public void registerContentObserver(Uri uri, boolean notifyDescendants,
+            ContentObserver settingsObserver) {
+        List<ContentObserver> observers;
+        mContentObserversAllUsers.putIfAbsent(uri.toString(), new ArrayList<>());
+        observers = mContentObserversAllUsers.get(uri.toString());
+        observers.add(settingsObserver);
+    }
+
+    @Override
+    public void unregisterContentObserver(ContentObserver settingsObserver) {
+        for (Map.Entry<String, List<ContentObserver>> entry :
+                mContentObserversAllUsers.entrySet()) {
+            entry.getValue().remove(settingsObserver);
+        }
+    }
+
+    @Override
+    public Uri getUriFor(String name) {
+        return Uri.withAppendedPath(CONTENT_URI, name);
+    }
+
+    @Override
+    public String getString(String name) {
+        return mValues.get(getUriFor(name).toString());
+    }
+
+    @Override
+    public boolean putString(String name, String value) {
+        return putString(name, value, null, false);
+    }
+
+    @Override
+    public boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag,
+            boolean makeDefault) {
+        String key = getUriFor(name).toString();
+        mValues.put(key, value);
+
+        Uri uri = getUriFor(name);
+        for (ContentObserver observer :
+                mContentObserversAllUsers.getOrDefault(uri.toString(), new ArrayList<>())) {
+            observer.dispatchChange(false, List.of(uri), 0);
+        }
+        return true;
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
index 4b97316..a491886 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
@@ -23,6 +23,8 @@
 import android.os.UserHandle;
 import android.util.Pair;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.settings.UserTracker;
 
 import java.util.ArrayList;
@@ -30,7 +32,7 @@
 import java.util.List;
 import java.util.Map;
 
-public class FakeSettings implements SecureSettings, GlobalSettings, SystemSettings {
+public class FakeSettings implements SecureSettings, SystemSettings {
     private final Map<SettingsKey, String> mValues = new HashMap<>();
     private final Map<SettingsKey, List<ContentObserver>> mContentObservers =
             new HashMap<>();
@@ -64,7 +66,7 @@
     }
 
     @Override
-    public void registerContentObserverForUser(Uri uri, boolean notifyDescendents,
+    public void registerContentObserverForUser(Uri uri, boolean notifyDescendants,
             ContentObserver settingsObserver, int userHandle) {
         List<ContentObserver> observers;
         if (userHandle == UserHandle.USER_ALL) {
@@ -147,7 +149,7 @@
     }
 
     @Override
-    public boolean putString(String name, String value, String tag, boolean makeDefault) {
+    public boolean putString(@NonNull String name, String value, String tag, boolean makeDefault) {
         return putString(name, value);
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockModule.kt
new file mode 100644
index 0000000..3e3d7cb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockModule.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.util.time
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+
+@Module
+interface FakeSystemClockModule {
+    @Binds fun bindFake(fake: FakeSystemClock): SystemClock
+
+    companion object {
+        @Provides @SysUISingleton fun providesFake() = FakeSystemClock()
+    }
+}
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index ced97cc..7ac7859 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -90,8 +90,14 @@
     // Make sound through the speaker.
     ALERT_BEEP = 2;
 
-    // Flash a notificaiton light.
+    // Flash a notification light.
     ALERT_BLINK = 4;
+
+    // Alert was attenuated by polite notif. feature.
+    ALERT_POLITE = 8;
+
+    // Alert was muted by polite notif. feature.
+    ALERT_MUTED = 16;
   }
 
   // Reasons that a notification might be dismissed.
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 21d0979..b403a7f 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -404,5 +404,9 @@
 
     // Notify the user that audio was lowered based on Calculated Sound Dose (CSD)
     NOTE_CSD_LOWER_AUDIO = 1007;
+
+    // Notify the user about external display events related to screenshot.
+    // Package: com.android.systemui
+    NOTE_GLOBAL_SCREENSHOT_EXTERNAL_DISPLAY = 1008;
   }
 }
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
new file mode 100644
index 0000000..91acc3d
--- /dev/null
+++ b/ravenwood/Android.bp
@@ -0,0 +1,33 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+    name: "ravenwood-annotations",
+    srcs: [
+        "annotations-src/**/*.java",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+// File that contains the standard command line arguments to hoststubgen.
+filegroup {
+    name: "ravenwood-standard-options",
+    srcs: [
+        "ravenwood-standard-options.txt",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+java_library {
+    name: "ravenwood-annotations-lib",
+    srcs: [":ravenwood-annotations"],
+    sdk_version: "core_current",
+    host_supported: true,
+    visibility: ["//visibility:public"],
+}
diff --git a/ravenwood/OWNERS b/ravenwood/OWNERS
new file mode 100644
index 0000000..c06b3b9
--- /dev/null
+++ b/ravenwood/OWNERS
@@ -0,0 +1,3 @@
+jsharkey@google.com
+omakoto@google.com
+jaggies@google.com
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.java
new file mode 100644
index 0000000..be7b923
--- /dev/null
+++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.ravenwood.annotations;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * Add this with a fully-specified method name (e.g. {@code "com.package.Class.methodName"})
+ * of a callback to get a callback at the class load time.
+ *
+ * The method must be {@code public static} with a single argument that takes
+ * {@link Class}.
+ */
+@Target({TYPE})
+@Retention(RetentionPolicy.CLASS)
+public @interface RavenwoodClassLoadHook {
+    String value();
+}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java
new file mode 100644
index 0000000..1644ffc
--- /dev/null
+++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java
@@ -0,0 +1,37 @@
+/*
+ * 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.ravenwood.annotations;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * TODO: Javadoc
+ *
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR})
+@Retention(RetentionPolicy.CLASS)
+public @interface RavenwoodKeep {
+}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java
new file mode 100644
index 0000000..eb883e2
--- /dev/null
+++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.ravenwood.annotations;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * TODO: Javadoc
+ */
+@Target({TYPE})
+@Retention(RetentionPolicy.CLASS)
+public @interface RavenwoodNativeSubstitutionClass {
+    String value();
+}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java
new file mode 100644
index 0000000..ffa1fa5
--- /dev/null
+++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java
@@ -0,0 +1,36 @@
+/*
+ * 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.ravenwood.annotations;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * TODO: Javadoc
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR})
+@Retention(RetentionPolicy.CLASS)
+public @interface RavenwoodRemove {
+}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java
new file mode 100644
index 0000000..6d747da
--- /dev/null
+++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java
@@ -0,0 +1,36 @@
+/*
+ * 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.ravenwood.annotations;
+
+import static java.lang.annotation.ElementType.METHOD;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * TODO: Javadoc
+ */
+@Target({METHOD})
+@Retention(RetentionPolicy.CLASS)
+public @interface RavenwoodSubstitute {
+    // TODO We should add "_host" as default. We're not doing it yet, because extractign the default
+    // value with ASM doesn't seem trivial. (? not sure.)
+    String suffix();
+}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.java
new file mode 100644
index 0000000..a329d84
--- /dev/null
+++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.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 android.ravenwood.annotations;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * TODO: Javadoc
+ * TODO: Create "whole-class-throw"?
+ */
+@Target({METHOD, CONSTRUCTOR})
+@Retention(RetentionPolicy.CLASS)
+public @interface RavenwoodThrow {
+}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java
new file mode 100644
index 0000000..ae6f42d
--- /dev/null
+++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java
@@ -0,0 +1,37 @@
+/*
+ * 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.ravenwood.annotations;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * TODO: Javadoc
+ * TODO: Create "whole-class-throw"?
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR})
+@Retention(RetentionPolicy.CLASS)
+public @interface RavenwoodWholeClassKeep {
+}
diff --git a/ravenwood/ravenwood-standard-options.txt b/ravenwood/ravenwood-standard-options.txt
new file mode 100644
index 0000000..6e1384f
--- /dev/null
+++ b/ravenwood/ravenwood-standard-options.txt
@@ -0,0 +1,37 @@
+# File containing standard options to HostStubGen for Ravenwood
+
+--debug
+
+# Keep all classes / methods / fields, but make the methods throw.
+--default-throw
+
+# Uncomment below lines to enable each feature.
+# --enable-non-stub-method-check
+
+#--default-method-call-hook
+#    com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+#--default-class-load-hook
+#    com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+
+# Standard annotations.
+# Note, each line is a single argument, so we need newlines after each `--xxx-annotation`.
+--keep-annotation
+    android.ravenwood.annotations.RavenwoodKeep
+
+--keep-class-annotation
+    android.ravenwood.annotations.RavenwoodWholeClassKeep
+
+--throw-annotation
+    android.ravenwood.annotations.RavenwoodThrow
+
+--remove-annotation
+    android.ravenwood.annotations.RavenwoodRemove
+
+--substitute-annotation
+    android.ravenwood.annotations.RavenwoodSubstitute
+
+--native-substitute-annotation
+    android.ravenwood.annotations.RavenwoodNativeSubstitutionClass
+
+--class-load-hook-annotation
+    android.ravenwood.annotations.RavenwoodClassLoadHook
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 6fa9c08..f09cb19 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -40,4 +40,25 @@
     namespace: "accessibility"
     description: "Whether to set min span of ScaleGestureDetector to zero."
     bug: "295327792"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "deprecate_package_list_observer"
+    namespace: "accessibility"
+    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 60d4ee6..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,34 +892,34 @@
         };
 
         // package changes
-        monitor.register(mContext, null,  UserHandle.ALL, true);
+        mPackageMonitor.register(mContext, null,  UserHandle.ALL, true);
 
-        // Register an additional observer for new packages using PackageManagerInternal, which
-        // generally notifies observers much sooner than the BroadcastReceiver-based PackageMonitor.
-        final PackageManagerInternal pm = LocalServices.getService(
-                PackageManagerInternal.class);
-        if (pm != null) {
-            pm.getPackageList(new PackageManagerInternal.PackageListObserver() {
-                @Override
-                public void onPackageAdded(String packageName, int uid) {
-                    final int userId = UserHandle.getUserId(uid);
-                    synchronized (mLock) {
-                        if (userId == mCurrentUserId) {
-                            onSomePackagesChangedLocked();
+        if (!Flags.deprecatePackageListObserver()) {
+            final PackageManagerInternal pm = LocalServices.getService(
+                    PackageManagerInternal.class);
+            if (pm != null) {
+                pm.getPackageList(new PackageManagerInternal.PackageListObserver() {
+                    @Override
+                    public void onPackageAdded(String packageName, int uid) {
+                        final int userId = UserHandle.getUserId(uid);
+                        synchronized (mLock) {
+                            if (userId == mCurrentUserId) {
+                                onSomePackagesChangedLocked();
+                            }
                         }
                     }
-                }
 
-                @Override
-                public void onPackageRemoved(String packageName, int uid) {
-                    final int userId = UserHandle.getUserId(uid);
-                    synchronized (mLock) {
-                        if (userId == mCurrentUserId) {
-                            onPackageRemovedLocked(packageName);
+                    @Override
+                    public void onPackageRemoved(String packageName, int uid) {
+                        final int userId = UserHandle.getUserId(uid);
+                        synchronized (mLock) {
+                            if (userId == mCurrentUserId) {
+                                onPackageRemovedLocked(packageName);
+                            }
                         }
                     }
-                }
-            });
+                });
+            }
         }
 
         // user change and unlock
@@ -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/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 30b9d0b..01064ac 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -873,7 +873,7 @@
 
                         transitionToDelegatingStateAndClear();
 
-                    } else if (mDetectTripleTap
+                    } else if (mDetectSingleFingerTripleTap
                             // If activated, delay an ACTION_DOWN for mMultiTapMaxDelay
                             // to ensure reachability of
                             // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
@@ -989,7 +989,7 @@
             // Shortcut acts as the 2 initial taps
             if (mShortcutTriggered) return tapCount() + 2 >= numTaps;
 
-            final boolean multitapTriggered = mDetectTripleTap
+            final boolean multitapTriggered = mDetectSingleFingerTripleTap
                     && tapCount() >= numTaps
                     && isMultiTap(mPreLastDown, mLastDown)
                     && isMultiTap(mPreLastUp, mLastUp);
@@ -1205,7 +1205,7 @@
          * @return true if tap is out of distance slop
          */
         boolean isTapOutOfDistanceSlop() {
-            if (!mDetectTripleTap) return false;
+            if (!mDetectSingleFingerTripleTap) return false;
             if (mPreLastDown == null || mLastDown == null) {
                 return false;
             }
@@ -1282,7 +1282,7 @@
                 + ", mMagnifiedInteractionState=" + mPanningScalingState
                 + ", mViewportDraggingState=" + mViewportDraggingState
                 + ", mSinglePanningState=" + mSinglePanningState
-                + ", mDetectTripleTap=" + mDetectTripleTap
+                + ", mDetectSingleFingerTripleTap=" + mDetectSingleFingerTripleTap
                 + ", mDetectShortcutTrigger=" + mDetectShortcutTrigger
                 + ", mCurrentState=" + State.nameOf(mCurrentState)
                 + ", mPreviousState=" + State.nameOf(mPreviousState)
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
index 2894693..8476a5e 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
@@ -57,11 +57,11 @@
     protected final boolean mDetectShortcutTrigger;
 
     /**
-     * {@code true} if this detector should detect and respond to triple-tap
+     * {@code true} if this detector should detect and respond to single-finger triple-tap
      * gestures for engaging and disengaging magnification,
      * {@code false} if it should ignore such gestures
      */
-    protected final boolean mDetectTripleTap;
+    protected final boolean mDetectSingleFingerTripleTap;
 
     /** Callback interface to report that magnification is interactive with a user. */
     public interface Callback {
@@ -85,12 +85,12 @@
     private final AccessibilityTraceManager mTrace;
     protected final Callback mCallback;
 
-    protected MagnificationGestureHandler(int displayId, boolean detectTripleTap,
+    protected MagnificationGestureHandler(int displayId, boolean detectSingleFingerTripleTap,
             boolean detectShortcutTrigger,
             AccessibilityTraceManager trace,
             @NonNull Callback callback) {
         mDisplayId = displayId;
-        mDetectTripleTap = detectTripleTap;
+        mDetectSingleFingerTripleTap = detectSingleFingerTripleTap;
         mDetectShortcutTrigger = detectShortcutTrigger;
         mTrace = trace;
         mCallback = callback;
@@ -128,7 +128,7 @@
     }
 
     private boolean shouldDispatchTransformedEvent(MotionEvent event) {
-        if ((!mDetectTripleTap && !mDetectShortcutTrigger) || !event.isFromSource(
+        if ((!mDetectSingleFingerTripleTap && !mDetectShortcutTrigger) || !event.isFromSource(
                 SOURCE_TOUCHSCREEN)) {
             return true;
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index c58e9a6..2d9dcb9 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -111,7 +111,7 @@
                 (event, rawEvent, policyFlags) -> dispatchTransformedEvent(event, rawEvent,
                         policyFlags));
         mDelegatingState = new DelegatingState(mMotionEventDispatcherDelegate);
-        mDetectingState = new DetectingState(context, mDetectTripleTap);
+        mDetectingState = new DetectingState(context);
         mViewportDraggingState = new ViewportDraggingState();
         mObservePanningScalingState = new PanningScalingGestureState(
                 new PanningScalingHandler(context, MAX_SCALE, MIN_SCALE, true,
@@ -448,22 +448,14 @@
 
         private final MagnificationGesturesObserver mGesturesObserver;
 
-        /**
-         * {@code true} if this detector should detect and respond to triple-tap
-         * gestures for engaging and disengaging magnification,
-         * {@code false} if it should ignore such gestures
-         */
-        private final boolean mDetectTripleTap;
-
-        DetectingState(@UiContext Context context, boolean detectTripleTap) {
-            mDetectTripleTap = detectTripleTap;
-            final MultiTap multiTap = new MultiTap(context, mDetectTripleTap ? 3 : 1,
-                    mDetectTripleTap
+        DetectingState(@UiContext Context context) {
+            final MultiTap multiTap = new MultiTap(context, mDetectSingleFingerTripleTap ? 3 : 1,
+                    mDetectSingleFingerTripleTap
                             ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP
                             : MagnificationGestureMatcher.GESTURE_SINGLE_TAP, null);
             final MultiTapAndHold multiTapAndHold = new MultiTapAndHold(context,
-                    mDetectTripleTap ? 3 : 1,
-                    mDetectTripleTap
+                    mDetectSingleFingerTripleTap ? 3 : 1,
+                    mDetectSingleFingerTripleTap
                             ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD
                             : MagnificationGestureMatcher.GESTURE_SINGLE_TAP_AND_HOLD, null);
             mGesturesObserver = new MagnificationGesturesObserver(this,
@@ -488,7 +480,7 @@
         @Override
         public boolean shouldStopDetection(MotionEvent motionEvent) {
             return !mWindowMagnificationMgr.isWindowMagnifierEnabled(mDisplayId)
-                    && !mDetectTripleTap;
+                    && !mDetectSingleFingerTripleTap;
         }
 
         @Override
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index 123b65c..b37bbd6 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -8,6 +8,13 @@
 }
 
 flag {
+  name: "fill_fields_from_current_session_only"
+  namespace: "autofill"
+  description: "Only fill autofill fields that are part of the current session."
+  bug: "270722825"
+}
+
+flag {
   name: "relayout"
   namespace: "autofill"
   description: "Mitigation for relayout issue"
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index c7b53c5..e4f1d3a 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -58,6 +58,7 @@
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.service.autofill.FillEventHistory;
+import android.service.autofill.Flags;
 import android.service.autofill.UserData;
 import android.text.TextUtils;
 import android.text.TextUtils.SimpleStringSplitter;
@@ -226,6 +227,12 @@
     @GuardedBy("mFlagLock")
     private int mMaxInputLengthForAutofill;
 
+    @GuardedBy("mFlagLock")
+    private boolean mAutofillCredmanIntegrationEnabled;
+
+    @GuardedBy("mFlagLock")
+    private boolean mIsFillFieldsFromCurrentSessionOnly;
+
     // Default flag values for Autofill PCC
 
     private static final String DEFAULT_PCC_FEATURE_PROVIDER_HINTS = "";
@@ -356,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:
@@ -701,12 +709,16 @@
                     DeviceConfig.NAMESPACE_AUTOFILL,
                     AutofillFeatureFlags.DEVICE_CONFIG_MAX_INPUT_LENGTH_FOR_AUTOFILL,
                     AutofillFeatureFlags.DEFAULT_MAX_INPUT_LENGTH_FOR_AUTOFILL);
+            mAutofillCredmanIntegrationEnabled = Flags.autofillCredmanIntegration();
+            mIsFillFieldsFromCurrentSessionOnly = Flags.fillFieldsFromCurrentSessionOnly();
             if (verbose) {
                 Slog.v(mTag, "setDeviceConfigProperties() for PCC: "
                         + "mPccClassificationEnabled=" + mPccClassificationEnabled
                         + ", mPccPreferProviderOverPcc=" + mPccPreferProviderOverPcc
                         + ", mPccUseFallbackDetection=" + mPccUseFallbackDetection
-                        + ", mPccProviderHints=" + mPccProviderHints);
+                        + ", mPccProviderHints=" + mPccProviderHints
+                        + ", mAutofillCredmanIntegrationEnabled="
+                        + mAutofillCredmanIntegrationEnabled);
             }
         }
     }
@@ -965,6 +977,15 @@
     }
 
     /**
+     * Whether the Autofill-Credman integration feature flag is enabled.
+     */
+    public boolean isAutofillCredmanIntegrationEnabled() {
+        synchronized (mFlagLock) {
+            return mAutofillCredmanIntegrationEnabled;
+        }
+    }
+
+    /**
      * Whether the Autofill Provider shouldbe preferred over PCC results for selecting datasets.
      */
     public boolean preferProviderOverPcc() {
@@ -1004,6 +1025,15 @@
         }
     }
 
+    /**
+     * Return if autofill should only fill in fields from current session.
+     */
+    public boolean getIsFillFieldsFromCurrentSessionOnly() {
+        synchronized (mFlagLock) {
+            return mIsFillFieldsFromCurrentSessionOnly;
+        }
+    }
+
     @Nullable
     @VisibleForTesting
     static Map<String, String[]> getAllowedCompatModePackages(String setting) {
@@ -2096,6 +2126,9 @@
                         pw.print(";");
                         pw.print("mPccProviderHints=");
                         pw.println(mPccProviderHints);
+                        pw.print(";");
+                        pw.print("mAutofillCredmanIntegrationEnabled=");
+                        pw.println(mAutofillCredmanIntegrationEnabled);
                     }
                     // Dump per-user services
                     dumpLocked("", pw);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 5b8bdd5..518b81f 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -19,6 +19,7 @@
 import static android.service.autofill.FillEventHistory.Event.NO_SAVE_UI_REASON_NONE;
 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
+import static android.service.autofill.FillRequest.FLAG_SCREEN_HAS_CREDMAN_FIELD;
 import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
 import static android.view.autofill.AutofillManager.FLAG_ADD_CLIENT_ENABLED;
 import static android.view.autofill.AutofillManager.FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY;
@@ -103,6 +104,10 @@
         extends AbstractPerUserSystemService<AutofillManagerServiceImpl, AutofillManagerService> {
 
     private static final String TAG = "AutofillManagerServiceImpl";
+
+    private static final ComponentName CREDMAN_SERVICE_COMPONENT_NAME =
+            new ComponentName("com.android.credentialmanager",
+                    "com.android.credentialmanager.autofill.CredentialAutofillService");
     private static final int MAX_SESSION_ID_CREATE_TRIES = 2048;
 
     /** Minimum interval to prune abandoned sessions */
@@ -532,9 +537,16 @@
 
         assertCallerLocked(clientActivity, compatMode);
 
-        // It's null when the session is just for augmented autofill
-        final ComponentName serviceComponentName = mInfo == null ? null
+        ComponentName serviceComponentName = mInfo == null ? null
                 : mInfo.getServiceInfo().getComponentName();
+
+        if (isAutofillCredmanIntegrationEnabled()
+                && ((flags & FLAG_SCREEN_HAS_CREDMAN_FIELD) != 0)) {
+            // Hardcode to credential manager proxy service
+            Slog.i(TAG, "Routing to CredentialAutofillService");
+            serviceComponentName = CREDMAN_SERVICE_COMPONENT_NAME;
+        }
+
         final Session newSession = new Session(this, mUi, getContext(), mHandler, mUserId, mLock,
                 sessionId, taskId, clientUid, clientActivityToken, clientCallback, hasCallback,
                 mUiLatencyHistory, mWtfHistory, serviceComponentName,
@@ -1747,6 +1759,10 @@
         }
     }
 
+    public boolean isAutofillCredmanIntegrationEnabled() {
+        return mMaster.isAutofillCredmanIntegrationEnabled();
+    }
+
     /**
      * Called when the {@link AutofillManagerService#mFieldClassificationResolver}
      * changed (among other places).
diff --git a/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java b/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java
deleted file mode 100644
index 715697d..0000000
--- a/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.autofill;
-
-import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
-
-import static com.android.server.autofill.Helper.sVerbose;
-
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.AppGlobals;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.ICancellationSignal;
-import android.os.RemoteException;
-import android.service.autofill.Dataset;
-import android.service.autofill.FillResponse;
-import android.service.autofill.IFillCallback;
-import android.service.autofill.SaveInfo;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.Slog;
-import android.view.autofill.AutofillId;
-import android.view.autofill.IAutoFillManagerClient;
-import android.view.inputmethod.InlineSuggestionsRequest;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.AndroidFuture;
-
-import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Maintains a client suggestions session with the
- * {@link android.view.autofill.AutofillRequestCallback} through the {@link IAutoFillManagerClient}.
- *
- */
-final class ClientSuggestionsSession {
-
-    private static final String TAG = "ClientSuggestionsSession";
-    private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 15 * DateUtils.SECOND_IN_MILLIS;
-
-    private final int mSessionId;
-    private final IAutoFillManagerClient mClient;
-    private final Handler mHandler;
-    private final ComponentName mComponentName;
-
-    private final RemoteFillService.FillServiceCallbacks mCallbacks;
-
-    private final Object mLock = new Object();
-    @GuardedBy("mLock")
-    private AndroidFuture<FillResponse> mPendingFillRequest;
-    @GuardedBy("mLock")
-    private int mPendingFillRequestId = INVALID_REQUEST_ID;
-
-    ClientSuggestionsSession(int sessionId, IAutoFillManagerClient client, Handler handler,
-            ComponentName componentName, RemoteFillService.FillServiceCallbacks callbacks) {
-        mSessionId = sessionId;
-        mClient = client;
-        mHandler = handler;
-        mComponentName = componentName;
-        mCallbacks = callbacks;
-    }
-
-    void onFillRequest(int requestId, InlineSuggestionsRequest inlineRequest, int flags) {
-        final AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
-        final AtomicReference<AndroidFuture<FillResponse>> futureRef = new AtomicReference<>();
-        final AndroidFuture<FillResponse> fillRequest = new AndroidFuture<>();
-
-        mHandler.post(() -> {
-            if (sVerbose) {
-                Slog.v(TAG, "calling onFillRequest() for id=" + requestId);
-            }
-
-            try {
-                mClient.requestFillFromClient(requestId, inlineRequest,
-                        new FillCallbackImpl(fillRequest, futureRef, cancellationSink));
-            } catch (RemoteException e) {
-                fillRequest.completeExceptionally(e);
-            }
-        });
-
-        fillRequest.orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
-        futureRef.set(fillRequest);
-
-        synchronized (mLock) {
-            mPendingFillRequest = fillRequest;
-            mPendingFillRequestId = requestId;
-        }
-
-        fillRequest.whenComplete((res, err) -> mHandler.post(() -> {
-            synchronized (mLock) {
-                mPendingFillRequest = null;
-                mPendingFillRequestId = INVALID_REQUEST_ID;
-            }
-            if (err == null) {
-                processAutofillId(res);
-                mCallbacks.onFillRequestSuccess(requestId, res,
-                        mComponentName.getPackageName(), flags);
-            } else {
-                Slog.e(TAG, "Error calling on  client fill request", err);
-                if (err instanceof TimeoutException) {
-                    dispatchCancellationSignal(cancellationSink.get());
-                    mCallbacks.onFillRequestTimeout(requestId);
-                } else if (err instanceof CancellationException) {
-                    dispatchCancellationSignal(cancellationSink.get());
-                } else {
-                    mCallbacks.onFillRequestFailure(requestId, err.getMessage());
-                }
-            }
-        }));
-    }
-
-    /**
-     * Gets the application info for the component.
-     */
-    @Nullable
-    static ApplicationInfo getAppInfo(ComponentName comp, @UserIdInt int userId) {
-        try {
-            ApplicationInfo si = AppGlobals.getPackageManager().getApplicationInfo(
-                    comp.getPackageName(),
-                    PackageManager.GET_META_DATA,
-                    userId);
-            if (si != null) {
-                return si;
-            }
-        } catch (RemoteException e) {
-        }
-        return null;
-    }
-
-    /**
-     * Gets the user-visible name of the application.
-     */
-    @Nullable
-    @GuardedBy("mLock")
-    static CharSequence getAppLabelLocked(Context context, ApplicationInfo appInfo) {
-        return appInfo == null ? null : appInfo.loadSafeLabel(
-                context.getPackageManager(), 0 /* do not ellipsize */,
-                TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
-    }
-
-    /**
-     * Gets the user-visible icon of the application.
-     */
-    @Nullable
-    @GuardedBy("mLock")
-    static Drawable getAppIconLocked(Context context, ApplicationInfo appInfo) {
-        return appInfo == null ? null : appInfo.loadIcon(context.getPackageManager());
-    }
-
-    int cancelCurrentRequest() {
-        synchronized (mLock) {
-            return mPendingFillRequest != null && mPendingFillRequest.cancel(false)
-                    ? mPendingFillRequestId
-                    : INVALID_REQUEST_ID;
-        }
-    }
-
-    /**
-     * The {@link AutofillId} which the client gets from its view is not contain the session id,
-     * but Autofill framework is using the {@link AutofillId} with a session id. So before using
-     * those ids in the Autofill framework, applies the current session id.
-     *
-     * @param res which response need to apply for a session id
-     */
-    private void processAutofillId(FillResponse res) {
-        if (res == null) {
-            return;
-        }
-
-        final List<Dataset> datasets = res.getDatasets();
-        if (datasets != null && !datasets.isEmpty()) {
-            for (int i = 0; i < datasets.size(); i++) {
-                final Dataset dataset = datasets.get(i);
-                if (dataset != null) {
-                    applySessionId(dataset.getFieldIds());
-                }
-            }
-        }
-
-        final SaveInfo saveInfo = res.getSaveInfo();
-        if (saveInfo != null) {
-            applySessionId(saveInfo.getOptionalIds());
-            applySessionId(saveInfo.getRequiredIds());
-            applySessionId(saveInfo.getSanitizerValues());
-            applySessionId(saveInfo.getTriggerId());
-        }
-    }
-
-    private void applySessionId(List<AutofillId> ids) {
-        if (ids == null || ids.isEmpty()) {
-            return;
-        }
-
-        for (int i = 0; i < ids.size(); i++) {
-            applySessionId(ids.get(i));
-        }
-    }
-
-    private void applySessionId(AutofillId[][] ids) {
-        if (ids == null) {
-            return;
-        }
-        for (int i = 0; i < ids.length; i++) {
-            applySessionId(ids[i]);
-        }
-    }
-
-    private void applySessionId(AutofillId[] ids) {
-        if (ids == null) {
-            return;
-        }
-        for (int i = 0; i < ids.length; i++) {
-            applySessionId(ids[i]);
-        }
-    }
-
-    private void applySessionId(AutofillId id) {
-        if (id == null) {
-            return;
-        }
-        id.setSessionId(mSessionId);
-    }
-
-    private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) {
-        if (signal == null) {
-            return;
-        }
-        try {
-            signal.cancel();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Error requesting a cancellation", e);
-        }
-    }
-
-    private class FillCallbackImpl extends IFillCallback.Stub {
-        final AndroidFuture<FillResponse> mFillRequest;
-        final AtomicReference<AndroidFuture<FillResponse>> mFutureRef;
-        final AtomicReference<ICancellationSignal> mCancellationSink;
-
-        FillCallbackImpl(AndroidFuture<FillResponse> fillRequest,
-                AtomicReference<AndroidFuture<FillResponse>> futureRef,
-                AtomicReference<ICancellationSignal> cancellationSink) {
-            mFillRequest = fillRequest;
-            mFutureRef = futureRef;
-            mCancellationSink = cancellationSink;
-        }
-
-        @Override
-        public void onCancellable(ICancellationSignal cancellation) {
-            AndroidFuture<FillResponse> future = mFutureRef.get();
-            if (future != null && future.isCancelled()) {
-                dispatchCancellationSignal(cancellation);
-            } else {
-                mCancellationSink.set(cancellation);
-            }
-        }
-
-        @Override
-        public void onSuccess(FillResponse response) {
-            mFillRequest.complete(response);
-        }
-
-        @Override
-        public void onFailure(int requestId, CharSequence message) {
-            String errorMessage = message == null ? "" : String.valueOf(message);
-            mFillRequest.completeExceptionally(
-                    new RuntimeException(errorMessage));
-        }
-    }
-}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 0220dec..07e9c50 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -16,7 +16,6 @@
 
 package com.android.server.autofill;
 
-import static android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS;
 import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
 import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE;
 import static android.service.autofill.Dataset.PICK_REASON_NO_PCC;
@@ -44,7 +43,6 @@
 import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
 import static android.view.autofill.AutofillManager.COMMIT_REASON_SESSION_DESTROYED;
 import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN;
-import static android.view.autofill.AutofillManager.FLAG_ENABLED_CLIENT_SUGGESTIONS;
 import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
 import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
 
@@ -110,8 +108,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
@@ -481,9 +477,6 @@
      */
     private final PccAssistDataReceiverImpl mPccAssistReceiver = new PccAssistDataReceiverImpl();
 
-    @Nullable
-    private ClientSuggestionsSession mClientSuggestionsSession;
-
     private final ClassificationState mClassificationState = new ClassificationState();
 
     // TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a
@@ -625,9 +618,6 @@
         /** Whether the current {@link FillResponse} is expired. */
         private boolean mExpiredResponse;
 
-        /** Whether the client is using {@link android.view.autofill.AutofillRequestCallback}. */
-        private boolean mClientSuggestionsEnabled;
-
         /** Whether the fill dialog UI is disabled. */
         private boolean mFillDialogDisabled;
 
@@ -673,19 +663,13 @@
                 }
                 mWaitForInlineRequest = inlineSuggestionsRequest != null;
                 mPendingInlineSuggestionsRequest = inlineSuggestionsRequest;
-                maybeRequestFillFromServiceLocked();
+                maybeRequestFillLocked();
                 viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
             }
         }
 
-        void newAutofillRequestLocked(@Nullable InlineSuggestionsRequest inlineRequest) {
-            mPendingFillRequest = null;
-            mWaitForInlineRequest = inlineRequest != null;
-            mPendingInlineSuggestionsRequest = inlineRequest;
-        }
-
         @GuardedBy("mLock")
-        void maybeRequestFillFromServiceLocked() {
+        void maybeRequestFillLocked() {
             if (mPendingFillRequest == null) {
                 return;
             }
@@ -696,15 +680,13 @@
                     return;
                 }
 
-                if (mPendingInlineSuggestionsRequest.isServiceSupported()) {
-                    mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
-                            mPendingFillRequest.getFillContexts(),
-                            mPendingFillRequest.getHints(),
-                            mPendingFillRequest.getClientState(),
-                            mPendingFillRequest.getFlags(),
-                            mPendingInlineSuggestionsRequest,
-                            mPendingFillRequest.getDelayedFillIntentSender());
-                }
+                mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
+                        mPendingFillRequest.getFillContexts(),
+                        mPendingFillRequest.getHints(),
+                        mPendingFillRequest.getClientState(),
+                        mPendingFillRequest.getFlags(),
+                        mPendingInlineSuggestionsRequest,
+                        mPendingFillRequest.getDelayedFillIntentSender());
             }
             mLastFillRequest = mPendingFillRequest;
 
@@ -826,7 +808,7 @@
                             : mDelayedFillPendingIntent.getIntentSender());
 
                 mPendingFillRequest = request;
-                maybeRequestFillFromServiceLocked();
+                maybeRequestFillLocked();
             }
 
             if (mActivityToken != null) {
@@ -1152,39 +1134,30 @@
     }
 
     /**
-     * Cancels the last request sent to the {@link #mRemoteFillService} or the
-     * {@link #mClientSuggestionsSession}.
+     * Cancels the last request sent to the {@link #mRemoteFillService}.
      */
     @GuardedBy("mLock")
     private void cancelCurrentRequestLocked() {
-        if (mRemoteFillService == null && mClientSuggestionsSession == null) {
-            wtf(null, "cancelCurrentRequestLocked() called without a remote service or a "
-                    + "client suggestions session.  mForAugmentedAutofillOnly: %s",
-                    mSessionFlags.mAugmentedAutofillOnly);
+        if (mRemoteFillService == null) {
+            wtf(null, "cancelCurrentRequestLocked() called without a remote service. "
+                    + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly);
             return;
         }
+        final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
 
-        if (mRemoteFillService != null) {
-            final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
+        // Remove the FillContext as there will never be a response for the service
+        if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
+            final int numContexts = mContexts.size();
 
-            // Remove the FillContext as there will never be a response for the service
-            if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
-                final int numContexts = mContexts.size();
-
-                // It is most likely the last context, hence search backwards
-                for (int i = numContexts - 1; i >= 0; i--) {
-                    if (mContexts.get(i).getRequestId() == canceledRequest) {
-                        if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
-                        mContexts.remove(i);
-                        break;
-                    }
+            // It is most likely the last context, hence search backwards
+            for (int i = numContexts - 1; i >= 0; i--) {
+                if (mContexts.get(i).getRequestId() == canceledRequest) {
+                    if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
+                    mContexts.remove(i);
+                    break;
                 }
             }
         }
-
-        if (mClientSuggestionsSession != null) {
-            mClientSuggestionsSession.cancelCurrentRequest();
-        }
     }
 
     private boolean isViewFocusedLocked(int flags) {
@@ -1280,30 +1253,16 @@
             requestAssistStructureForPccLocked(flags | FLAG_PCC_DETECTION);
         }
 
-        // Only ask IME to create inline suggestions request when
-        // 1. Autofill provider supports it or client enabled client suggestions.
-        // 2. The render service is available.
-        // 3. The view is focused. (The view may not be focused if the autofill is triggered
-        //    manually.)
+        // Only ask IME to create inline suggestions request if Autofill provider supports it and
+        // the render service is available except the autofill is triggered manually and the view
+        // is also not focused.
         final RemoteInlineSuggestionRenderService remoteRenderService =
                 mService.getRemoteInlineSuggestionRenderServiceLocked();
-        if ((mSessionFlags.mInlineSupportedByService || mSessionFlags.mClientSuggestionsEnabled)
-                && remoteRenderService != null
-                && (isViewFocusedLocked(flags) || (isRequestSupportFillDialog(flags)))) {
-            final Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer;
-            if (mSessionFlags.mClientSuggestionsEnabled) {
-                final int finalRequestId = requestId;
-                inlineSuggestionsRequestConsumer = (inlineSuggestionsRequest) -> {
-                    // Using client suggestions
-                    synchronized (mLock) {
-                        onClientFillRequestLocked(finalRequestId, inlineSuggestionsRequest);
-                    }
-                    viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
-                };
-            } else {
-                inlineSuggestionsRequestConsumer = mAssistReceiver.newAutofillRequestLocked(
-                        viewState, /* isInlineRequest= */ true);
-            }
+        if (mSessionFlags.mInlineSupportedByService && remoteRenderService != null
+            && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) {
+            Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
+                mAssistReceiver.newAutofillRequestLocked(viewState,
+                    /* isInlineRequest= */ true);
             if (inlineSuggestionsRequestConsumer != null) {
                 final int requestIdCopy = requestId;
                 final AutofillId focusedId = mCurrentViewId;
@@ -1323,18 +1282,10 @@
                         inlineSuggestionRendorInfoCallback);
                 viewState.setState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
             }
-        } else if (mSessionFlags.mClientSuggestionsEnabled) {
-            // Request client suggestions for the dropdown mode
-            onClientFillRequestLocked(requestId, null);
         } else {
             mAssistReceiver.newAutofillRequestLocked(viewState, /* isInlineRequest= */ false);
         }
 
-        if (mSessionFlags.mClientSuggestionsEnabled) {
-            // Using client suggestions, unnecessary request AssistStructure
-            return;
-        }
-
         // Now request the assist structure data.
         requestAssistStructureLocked(requestId, flags);
     }
@@ -1443,11 +1394,6 @@
             mSessionFlags = new SessionFlags();
             mSessionFlags.mAugmentedAutofillOnly = forAugmentedAutofillOnly;
             mSessionFlags.mInlineSupportedByService = mService.isInlineSuggestionsEnabledLocked();
-            if (mContext.checkCallingPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
-                    == PackageManager.PERMISSION_GRANTED) {
-                mSessionFlags.mClientSuggestionsEnabled =
-                        (mFlags & FLAG_ENABLED_CLIENT_SUGGESTIONS) != 0;
-            }
             setClientLocked(client);
         }
 
@@ -1599,15 +1545,14 @@
                 if (requestLog != null) {
                     requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, -1);
                 }
-                processNullResponseOrFallbackLocked(requestId, requestFlags);
+                processNullResponseLocked(requestId, requestFlags);
                 return;
             }
 
             // TODO: Check if this is required. We can still present datasets to the user even if
             //  traditional field classification is disabled.
             fieldClassificationIds = response.getFieldClassificationIds();
-            if (!mSessionFlags.mClientSuggestionsEnabled && fieldClassificationIds != null
-                    && !mService.isFieldClassificationEnabledLocked()) {
+            if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) {
                 Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
                 processNullResponseLocked(requestId, requestFlags);
                 return;
@@ -1741,9 +1686,7 @@
                         || (ArrayUtils.isEmpty(saveInfo.getOptionalIds())
                             && ArrayUtils.isEmpty(saveInfo.getRequiredIds())
                             && ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) == 0)))
-                    && (ArrayUtils.isEmpty(response.getFieldClassificationIds())
-                        || (!mSessionFlags.mClientSuggestionsEnabled
-                        && !mService.isFieldClassificationEnabledLocked())));
+                    && (ArrayUtils.isEmpty(response.getFieldClassificationIds())));
         }
     }
 
@@ -2190,40 +2133,6 @@
         fieldFilters.add(dataset.getFilter(index));
     }
 
-    @GuardedBy("mLock")
-    private void processNullResponseOrFallbackLocked(int requestId, int flags) {
-        if (!mSessionFlags.mClientSuggestionsEnabled) {
-            processNullResponseLocked(requestId, flags);
-            return;
-        }
-
-        // fallback to the default platform password manager
-        mSessionFlags.mClientSuggestionsEnabled = false;
-        mLastFillDialogTriggerIds = null;
-        // Log the existing FillResponse event.
-        mFillResponseEventLogger.logAndEndEvent();
-
-        final InlineSuggestionsRequest inlineRequest =
-                (mLastInlineSuggestionsRequest != null
-                        && mLastInlineSuggestionsRequest.first == requestId)
-                        ? mLastInlineSuggestionsRequest.second : null;
-
-        // Start a new FillRequest logger for client suggestion fallback.
-        mFillRequestEventLogger.startLogForNewRequest();
-        mRequestCount++;
-        mFillRequestEventLogger.maybeSetAppPackageUid(uid);
-        mFillRequestEventLogger.maybeSetFlags(
-            flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS);
-        mFillRequestEventLogger.maybeSetRequestTriggerReason(
-            TRIGGER_REASON_NORMAL_TRIGGER);
-        mFillRequestEventLogger.maybeSetIsClientSuggestionFallback(true);
-
-        mAssistReceiver.newAutofillRequestLocked(inlineRequest);
-        requestAssistStructureLocked(requestId,
-                flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS);
-        return;
-    }
-
     // FillServiceCallbacks
     @Override
     @SuppressWarnings("GuardedBy")
@@ -2437,7 +2346,7 @@
                         + id + " destroyed");
                 return;
             }
-            fillInIntent = createAuthFillInIntentLocked(requestId, extras);
+            fillInIntent = createAuthFillInIntentLocked(requestId, extras, /* authExtras= */ null);
             if (fillInIntent == null) {
                 forceRemoveFromServiceLocked();
                 return;
@@ -4520,22 +4429,13 @@
             filterText = value.getTextValue().toString();
         }
 
-        final CharSequence targetLabel;
-        final Drawable targetIcon;
-        synchronized (mLock) {
-            if (mSessionFlags.mClientSuggestionsEnabled) {
-                final ApplicationInfo appInfo = ClientSuggestionsSession.getAppInfo(mComponentName,
-                        mService.getUserId());
-                targetLabel = ClientSuggestionsSession.getAppLabelLocked(
-                        mService.getMaster().getContext(), appInfo);
-                targetIcon = ClientSuggestionsSession.getAppIconLocked(
-                        mService.getMaster().getContext(), appInfo);
-            } else {
-                targetLabel = mService.getServiceLabelLocked();
-                targetIcon = mService.getServiceIconLocked();
-            }
+        final CharSequence serviceLabel;
+        final Drawable serviceIcon;
+        synchronized (this.mService.mLock) {
+            serviceLabel = mService.getServiceLabelLocked();
+            serviceIcon = mService.getServiceIconLocked();
         }
-        if (targetLabel == null || targetIcon == null) {
+        if (serviceLabel == null || serviceIcon == null) {
             wtf(null, "onFillReady(): no service label or icon");
             return;
         }
@@ -4596,7 +4496,7 @@
 
         getUiForShowing().showFillUi(filledId, response, filterText,
                 mService.getServicePackageName(), mComponentName,
-                targetLabel, targetIcon, this, mContext, id, mCompatMode,
+                serviceLabel, serviceIcon, this, mContext, id, mCompatMode,
                 mService.getMaster().getMaxInputLengthForAutofill());
 
         synchronized (mLock) {
@@ -4799,17 +4699,6 @@
             return false;
         }
 
-        final InlineSuggestionsRequest request = inlineSuggestionsRequest.get();
-        if (mSessionFlags.mClientSuggestionsEnabled && !request.isClientSupported()
-                || !mSessionFlags.mClientSuggestionsEnabled && !request.isServiceSupported()) {
-            if (sDebug) {
-                Slog.d(TAG, "Inline suggestions not supported for "
-                        + (mSessionFlags.mClientSuggestionsEnabled ? "client" : "service")
-                        + ". Falling back to dropdown.");
-            }
-            return false;
-        }
-
         final RemoteInlineSuggestionRenderService remoteRenderService =
                 mService.getRemoteInlineSuggestionRenderServiceLocked();
         if (remoteRenderService == null) {
@@ -4824,7 +4713,7 @@
         }
 
         final InlineFillUi.InlineFillUiInfo inlineFillUiInfo =
-                new InlineFillUi.InlineFillUiInfo(request, focusedId,
+                new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId,
                         filterText, remoteRenderService, userId, id);
         InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response,
                 new InlineFillUi.InlineSuggestionUiCallback() {
@@ -5558,7 +5447,8 @@
             mPresentationStatsEventLogger.maybeSetAuthenticationType(
                 AUTHENTICATION_TYPE_DATASET_AUTHENTICATION);
             setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false);
-            final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState);
+            final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState,
+                    dataset.getAuthenticationExtras());
             if (fillInIntent == null) {
                 forceRemoveFromServiceLocked();
                 return;
@@ -5574,7 +5464,8 @@
     // TODO: this should never be null, but we got at least one occurrence, probably due to a race.
     @GuardedBy("mLock")
     @Nullable
-    private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) {
+    private Intent createAuthFillInIntentLocked(int requestId, Bundle extras,
+            @Nullable Bundle authExtras) {
         final Intent fillInIntent = new Intent();
 
         final FillContext context = getFillContextByRequestIdLocked(requestId);
@@ -5591,6 +5482,9 @@
         }
         fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure());
         fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, extras);
+        if (authExtras != null) {
+            fillInIntent.putExtra(AutofillManager.EXTRA_AUTH_STATE, authExtras);
+        }
         return fillInIntent;
     }
 
@@ -5636,26 +5530,6 @@
         }
     }
 
-    @GuardedBy("mLock")
-    private void onClientFillRequestLocked(int requestId,
-            InlineSuggestionsRequest inlineSuggestionsRequest) {
-        if (mClientSuggestionsSession == null) {
-            mClientSuggestionsSession = new ClientSuggestionsSession(id, mClient, mHandler,
-                    mComponentName, this);
-        }
-
-        if (mContexts == null) {
-            mContexts = new ArrayList<>(1);
-        }
-        mContexts.add(new FillContext(requestId, new AssistStructure(), mCurrentViewId));
-
-        if (inlineSuggestionsRequest != null && !inlineSuggestionsRequest.isClientSupported()) {
-            inlineSuggestionsRequest = null;
-        }
-
-        mClientSuggestionsSession.onFillRequest(requestId, inlineSuggestionsRequest, mFlags);
-    }
-
     /**
      * The result of checking whether to show the save dialog, when session can be saved.
      *
@@ -6137,9 +6011,17 @@
                         continue;
                     }
                     final AutofillId viewId = dataset.getFieldIds().get(i);
+                    final ViewState viewState = mViewStates.get(viewId);
+                    if (mService.getMaster().getIsFillFieldsFromCurrentSessionOnly()
+                            && viewState != null && viewState.id.getSessionId() != id) {
+                        if (sVerbose) {
+                            Slog.v(TAG, "Skipping filling view: " +
+                                    viewId + " as it isn't part of the current session: " + id);
+                        }
+                        continue;
+                    }
                     ids.add(viewId);
                     values.add(dataset.getFieldValues().get(i));
-                    final ViewState viewState = mViewStates.get(viewId);
                     if (viewState != null
                             && (viewState.getState() & ViewState.STATE_WAITING_DATASET_AUTH) != 0) {
                         if (sVerbose) {
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..4298c07 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
@@ -632,6 +632,10 @@
     public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
             @VirtualDeviceParams.DevicePolicy int devicePolicy) {
         super.setDevicePolicy_enforcePermission();
+        if (!Flags.dynamicPolicy()) {
+            return;
+        }
+
         switch (policyType) {
             case POLICY_TYPE_RECENTS:
                 synchronized (mVirtualDeviceLock) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index cfe56e9..5cb100a1 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -38,6 +38,7 @@
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.flags.Flags;
 import android.companion.virtual.sensor.VirtualSensor;
+import android.companion.virtualnative.IVirtualDeviceManagerNative;
 import android.content.AttributionSource;
 import android.content.Context;
 import android.content.Intent;
@@ -88,8 +89,11 @@
 
     private static final String TAG = "VirtualDeviceManagerService";
 
+    private static final String VIRTUAL_DEVICE_NATIVE_SERVICE = "virtualdevice_native";
+
     private final Object mVirtualDeviceManagerLock = new Object();
     private final VirtualDeviceManagerImpl mImpl;
+    private final VirtualDeviceManagerNativeImpl mNativeImpl;
     private final VirtualDeviceManagerInternal mLocalService;
     private VirtualDeviceLog mVirtualDeviceLog = new VirtualDeviceLog(getContext());
     private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -125,6 +129,7 @@
     public VirtualDeviceManagerService(Context context) {
         super(context);
         mImpl = new VirtualDeviceManagerImpl();
+        mNativeImpl = Flags.enableNativeVdm() ? new VirtualDeviceManagerNativeImpl() : null;
         mLocalService = new LocalService();
     }
 
@@ -155,6 +160,9 @@
     @Override
     public void onStart() {
         publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl);
+        if (Flags.enableNativeVdm()) {
+            publishBinderService(VIRTUAL_DEVICE_NATIVE_SERVICE, mNativeImpl);
+        }
         publishLocalService(VirtualDeviceManagerInternal.class, mLocalService);
         ActivityTaskManagerInternal activityTaskManagerInternal = getLocalService(
                 ActivityTaskManagerInternal.class);
@@ -590,6 +598,19 @@
         }
     }
 
+    final class VirtualDeviceManagerNativeImpl extends IVirtualDeviceManagerNative.Stub {
+        @Override // Binder call
+        public int[] getDeviceIdsForUid(int uid) {
+            return mLocalService
+                    .getDeviceIdsForUid(uid).stream().mapToInt(Integer::intValue).toArray();
+        }
+
+        @Override // Binder call
+        public int getDevicePolicy(int deviceId, int policyType) {
+            return mImpl.getDevicePolicy(deviceId, policyType);
+        }
+    }
+
     private final class LocalService extends VirtualDeviceManagerInternal {
         @GuardedBy("mVirtualDeviceManagerLock")
         private final ArrayList<VirtualDisplayListener>
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/Android.bp b/services/core/Android.bp
index 6521fab..e5225f6 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -183,6 +183,7 @@
         "cbor-java",
         "display_flags_lib",
         "icu4j_calendar_astronomer",
+        "android.security.aaid_aidl-java",
         "netd-client",
         "overlayable_policy_aidl-java",
         "SurfaceFlingerProperties",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index cd87908..8df5456 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1428,6 +1428,12 @@
             @UserIdInt int userId);
 
     /**
+     * Sends the PACKAGE_RESTARTED broadcast on the package manager handler thread.
+     */
+    public abstract void sendPackageRestartedBroadcast(@NonNull String packageName,
+            int uid, @Intent.Flags int flags);
+
+    /**
      * Return a list of all historical install sessions for the given user.
      */
     public abstract ParceledListSlice<PackageInstaller.SessionInfo> getHistoricalSessions(
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 556eba6..e9d4d76 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import static android.os.Flags.stateOfHealthPublic;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import static com.android.server.health.Utils.copyV1Battery;
 
@@ -27,7 +28,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.database.ContentObserver;
 import android.hardware.health.HealthInfo;
 import android.hardware.health.V2_1.BatteryCapacityLevel;
@@ -1333,10 +1333,14 @@
         @Override
         public int getProperty(int id, final BatteryProperty prop) throws RemoteException {
             switch (id) {
+                case BatteryManager.BATTERY_PROPERTY_STATE_OF_HEALTH:
+                    if (stateOfHealthPublic()) {
+                        break;
+                    }
+
                 case BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE:
                 case BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE:
                 case BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY:
-                case BatteryManager.BATTERY_PROPERTY_STATE_OF_HEALTH:
                     mContext.enforceCallingPermission(
                             android.Manifest.permission.BATTERY_STATS, null);
                     break;
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index d47a399..db89cac 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -87,7 +87,7 @@
 # replaces 27510 with a row per notification
 27531 notification_visibility (key|3),(visibile|1),(lifespan|1),(freshness|1),(exposure|1),(rank|1)
 # a notification emited noise, vibration, or light
-27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1)
+27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1),(politeness|1)
 # a notification was added to a autogroup
 27533 notification_autogrouped (key|3)
 # notification was removed from an autogroup
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index 93dca2f..57ed5a2 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -41,6 +41,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
@@ -153,6 +154,9 @@
     @GuardedBy("this")
     private ArraySet<Integer> mPinKeys;
 
+    private static final String DEVICE_CONFIG_KEY_ANON_SIZE = "pin_shared_anon_size";
+    private static final long DEFAULT_ANON_SIZE =
+            SystemProperties.getLong("pinner.pin_shared_anon_size", 0);
     private static final long MAX_ANON_SIZE = 2L * (1L << 30); // 2GB
     private long mPinAnonSize;
     private long mPinAnonAddress;
@@ -180,6 +184,17 @@
         }
     };
 
+    private DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener =
+            new DeviceConfig.OnPropertiesChangedListener() {
+                @Override
+                public void onPropertiesChanged(DeviceConfig.Properties properties) {
+                    if (DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT.equals(properties.getNamespace())
+                            && properties.getKeyset().contains(DEVICE_CONFIG_KEY_ANON_SIZE)) {
+                        refreshPinAnonConfig();
+                    }
+                }
+            };
+
     public PinnerService(Context context) {
         super(context);
 
@@ -206,6 +221,11 @@
 
         registerUidListener();
         registerUserSetupCompleteListener();
+
+        DeviceConfig.addOnPropertiesChangedListener(
+                DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
+                new HandlerExecutor(mPinnerHandler),
+                mDeviceConfigListener);
     }
 
     @Override
@@ -344,6 +364,8 @@
                 }
             }
         }
+
+        refreshPinAnonConfig();
     }
 
     /**
@@ -560,11 +582,6 @@
             pinKeys.add(KEY_ASSISTANT);
         }
 
-        mPinAnonSize = DeviceConfig.getLong(DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
-                "pin_anon_size",
-                SystemProperties.getLong("pinner.pin_anon_size", 0));
-        mPinAnonSize = Math.max(0, Math.min(mPinAnonSize, MAX_ANON_SIZE));
-
         return pinKeys;
     }
 
@@ -604,7 +621,6 @@
             int key = currentPinKeys.valueAt(i);
             pinApp(key, userHandle, true /* force */);
         }
-        pinAnonRegion();
     }
 
     /**
@@ -689,10 +705,28 @@
     }
 
     /**
+     * Handle any changes in the anon region pinner config.
+     */
+    private void refreshPinAnonConfig() {
+        long newPinAnonSize =
+                DeviceConfig.getLong(
+                        DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
+                        DEVICE_CONFIG_KEY_ANON_SIZE,
+                        DEFAULT_ANON_SIZE);
+        newPinAnonSize = Math.max(0, Math.min(newPinAnonSize, MAX_ANON_SIZE));
+        if (newPinAnonSize != mPinAnonSize) {
+            mPinAnonSize = newPinAnonSize;
+            pinAnonRegion();
+        }
+    }
+
+    /**
      * Pin an empty anonymous region. This should only be used for ablation experiments.
      */
     private void pinAnonRegion() {
         if (mPinAnonSize == 0) {
+            Slog.d(TAG, "pinAnonRegion: releasing pinned region");
+            unpinAnonRegion();
             return;
         }
         long alignedPinSize = mPinAnonSize;
@@ -700,15 +734,21 @@
             alignedPinSize -= alignedPinSize % PAGE_SIZE;
             Slog.e(TAG, "pinAnonRegion: aligning size to " + alignedPinSize);
         }
-        if (mPinAnonAddress != 0
-                && mCurrentlyPinnedAnonSize != alignedPinSize) {
+        if (mPinAnonAddress != 0) {
+            if (mCurrentlyPinnedAnonSize == alignedPinSize) {
+                Slog.d(TAG, "pinAnonRegion: already pinned region of size " + alignedPinSize);
+                return;
+            }
+            Slog.d(TAG, "pinAnonRegion: resetting pinned region for new size " + alignedPinSize);
             unpinAnonRegion();
         }
         long address = 0;
         try {
+            // Map as SHARED to avoid changing rss.anon for system_server (per /proc/*/status).
+            // The mapping is visible in other rss metrics, and as private dirty in smaps/meminfo.
             address = Os.mmap(0, alignedPinSize,
                     OsConstants.PROT_READ | OsConstants.PROT_WRITE,
-                    OsConstants.MAP_PRIVATE | OsConstants.MAP_ANONYMOUS,
+                    OsConstants.MAP_SHARED | OsConstants.MAP_ANONYMOUS,
                     new FileDescriptor(), /*offset=*/0);
 
             Unsafe tempUnsafe = null;
@@ -729,7 +769,7 @@
             mCurrentlyPinnedAnonSize = alignedPinSize;
             mPinAnonAddress = address;
             address = -1;
-            Slog.e(TAG, "pinAnonRegion success, size=" + mCurrentlyPinnedAnonSize);
+            Slog.w(TAG, "pinAnonRegion success, size=" + mCurrentlyPinnedAnonSize);
         } catch (Exception ex) {
             Slog.e(TAG, "Could not pin anon region of size " + alignedPinSize, ex);
             return;
@@ -744,6 +784,8 @@
         if (mPinAnonAddress != 0) {
             safeMunmap(mPinAnonAddress, mCurrentlyPinnedAnonSize);
         }
+        mPinAnonAddress = 0;
+        mCurrentlyPinnedAnonSize = 0;
     }
 
     /**
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/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 330742a..5fb889a 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5023,7 +5023,7 @@
             p.setDataPosition(0);
             Bundle simulateBundle = p.readBundle();
             p.recycle();
-            Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
+            Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
             if (intent != null && intent.getClass() != Intent.class) {
                 return false;
             }
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 d3ce47c..b43b986 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -377,6 +377,7 @@
 import android.util.IndentingPrintWriter;
 import android.util.IntArray;
 import android.util.Log;
+import android.util.MathUtils;
 import android.util.Pair;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
@@ -562,7 +563,7 @@
     static final int PROC_START_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
 
     // How long we wait for a launched process to complete its app startup before we ANR.
-    static final int BIND_APPLICATION_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+    static final int BIND_APPLICATION_TIMEOUT = 15 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
 
     // How long we wait to kill an application zygote, after the last process using
     // it has gone away.
@@ -1531,6 +1532,11 @@
      */
     int mBootPhase;
 
+    /**
+     * The time stamp that all apps have received BOOT_COMPLETED.
+     */
+    volatile long mBootCompletedTimestamp;
+
     @GuardedBy("this")
     boolean mDeterministicUidIdle = false;
 
@@ -1630,7 +1636,8 @@
     static final int UPDATE_CACHED_APP_HIGH_WATERMARK = 79;
     static final int ADD_UID_TO_OBSERVER_MSG = 80;
     static final int REMOVE_UID_FROM_OBSERVER_MSG = 81;
-    static final int BIND_APPLICATION_TIMEOUT_MSG = 82;
+    static final int BIND_APPLICATION_TIMEOUT_SOFT_MSG = 82;
+    static final int BIND_APPLICATION_TIMEOUT_HARD_MSG = 83;
 
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
 
@@ -1983,15 +1990,11 @@
                 case UPDATE_CACHED_APP_HIGH_WATERMARK: {
                     mAppProfiler.mCachedAppsWatermarkData.updateCachedAppsSnapshot((long) msg.obj);
                 } break;
-                case BIND_APPLICATION_TIMEOUT_MSG: {
-                    ProcessRecord app = (ProcessRecord) msg.obj;
-
-                    final String anrMessage;
-                    synchronized (app) {
-                        anrMessage = "Process " + app + " failed to complete startup";
-                    }
-
-                    mAnrHelper.appNotResponding(app, TimeoutRecord.forAppStart(anrMessage));
+                case BIND_APPLICATION_TIMEOUT_SOFT_MSG: {
+                    handleBindApplicationTimeoutSoft((ProcessRecord) msg.obj, msg.arg1);
+                } break;
+                case BIND_APPLICATION_TIMEOUT_HARD_MSG: {
+                    handleBindApplicationTimeoutHard((ProcessRecord) msg.obj);
                 } break;
             }
         }
@@ -4160,26 +4163,34 @@
 
     @GuardedBy("this")
     private void finishForceStopPackageLocked(final String packageName, int uid) {
-        Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED,
-                Uri.fromParts("package", packageName, null));
+        int flags = 0;
         if (!mProcessesReady) {
-            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                    | Intent.FLAG_RECEIVER_FOREGROUND);
+            flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                    | Intent.FLAG_RECEIVER_FOREGROUND;
         }
-        final int userId = UserHandle.getUserId(uid);
-        final int[] broadcastAllowList =
-                getPackageManagerInternal().getVisibilityAllowList(packageName, userId);
-        intent.putExtra(Intent.EXTRA_UID, uid);
-        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-        broadcastIntentLocked(null /* callerApp */, null /* callerPackage */,
-                null /* callerFeatureId */, intent, null /* resolvedType */,
-                null /* resultToApp */, null /* resultTo */,
-                0 /* resultCode */, null /* resultData */, null /* resultExtras */,
-                null /* requiredPermissions */, null /* excludedPermissions */,
-                null /* excludedPackages */, OP_NONE, null /* bOptions */, false /* ordered */,
-                false /* sticky */, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
-                Binder.getCallingPid(), userId, BackgroundStartPrivileges.NONE,
-                broadcastAllowList, null /* filterExtrasForReceiver */);
+        if (android.content.pm.Flags.stayStopped()) {
+            // Sent async using the PM handler, to maintain ordering with PACKAGE_UNSTOPPED
+            mPackageManagerInt.sendPackageRestartedBroadcast(packageName,
+                    uid, flags);
+        } else {
+            Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED,
+                    Uri.fromParts("package", packageName, null));
+            intent.addFlags(flags);
+            final int userId = UserHandle.getUserId(uid);
+            final int[] broadcastAllowList =
+                    getPackageManagerInternal().getVisibilityAllowList(packageName, userId);
+            intent.putExtra(Intent.EXTRA_UID, uid);
+            intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+            broadcastIntentLocked(null /* callerApp */, null /* callerPackage */,
+                    null /* callerFeatureId */, intent, null /* resolvedType */,
+                    null /* resultToApp */, null /* resultTo */,
+                    0 /* resultCode */, null /* resultData */, null /* resultExtras */,
+                    null /* requiredPermissions */, null /* excludedPermissions */,
+                    null /* excludedPackages */, OP_NONE, null /* bOptions */, false /* ordered */,
+                    false /* sticky */, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
+                    Binder.getCallingPid(), userId, BackgroundStartPrivileges.NONE,
+                    broadcastAllowList, null /* filterExtrasForReceiver */);
+        }
     }
 
     private void cleanupDisabledPackageComponentsLocked(
@@ -4749,6 +4760,7 @@
                 mPlatformCompat.resetReporting(app.info);
             }
             final ProviderInfoList providerList = ProviderInfoList.fromList(providers);
+            app.mProfile.mLastCpuDelayTime.set(app.getCpuDelayTime());
             if (app.getIsolatedEntryPoint() != null) {
                 // This is an isolated process which should just call an entry point instead of
                 // being bound to an application.
@@ -4786,9 +4798,10 @@
                         app.getStartElapsedTime(), app.getStartUptime());
             }
 
-            Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_MSG);
+            Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_SOFT_MSG);
             msg.obj = app;
-            mHandler.sendMessageDelayed(msg, BIND_APPLICATION_TIMEOUT);
+            msg.arg1 = BIND_APPLICATION_TIMEOUT;
+            mHandler.sendMessageDelayed(msg, msg.arg1 /* BIND_APPLICATION_TIMEOUT */);
             mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
 
             if (profilerInfo != null) {
@@ -4865,7 +4878,8 @@
         }
 
         if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) {
-            mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_MSG, app);
+            mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_SOFT_MSG, app);
+            mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_HARD_MSG, app);
         } else {
             Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid
                     + ". Uid: " + uid);
@@ -5002,6 +5016,35 @@
         }
     }
 
+    private void handleBindApplicationTimeoutSoft(ProcessRecord app, int softTimeoutMillis) {
+        // Similar logic as the broadcast delivery timeout:
+        // instead of immediately triggering an ANR, extend the timeout by
+        // the amount of time the process was runnable-but-waiting; we're
+        // only willing to do this once before triggering an hard ANR.
+        final long cpuDelayTime = app.getCpuDelayTime() - app.mProfile.mLastCpuDelayTime.get();
+        final long hardTimeoutMillis = MathUtils.constrain(cpuDelayTime, 0, softTimeoutMillis);
+
+        if (hardTimeoutMillis == 0) {
+            handleBindApplicationTimeoutHard(app);
+            return;
+        }
+
+        Slog.i(TAG, "Extending process start timeout by " + hardTimeoutMillis + "ms for " + app);
+        Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplicationTimeSoft "
+                + app.processName + "(" + app.getPid() + ")");
+        final Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_HARD_MSG, app);
+        mHandler.sendMessageDelayed(msg, hardTimeoutMillis);
+    }
+
+    private void handleBindApplicationTimeoutHard(ProcessRecord app) {
+        final String anrMessage;
+        synchronized (app) {
+            anrMessage = "Process " + app + " failed to complete startup";
+        }
+
+        mAnrHelper.appNotResponding(app, TimeoutRecord.forAppStart(anrMessage));
+    }
+
     /**
      * @return The last part of the string of an intent's action.
      */
@@ -5126,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/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 928b5d8..3cf4332 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1205,6 +1205,8 @@
             trackerMemFactor = mService.mProcessStats.getMemFactorLocked();
         }
 
+        mLastMemoryLevel = memFactor;
+        mLastNumProcesses = mService.mProcessList.getLruSizeLOSP();
         if (mService.mConstants.USE_MODERN_TRIM) {
             // Modern trim is not sent based on lowmem state
             // Dispatch UI_HIDDEN to processes that need it
@@ -1235,8 +1237,6 @@
             return false;
         }
 
-        mLastMemoryLevel = memFactor;
-        mLastNumProcesses = mService.mProcessList.getLruSizeLOSP();
         if (memFactor != ADJ_MEM_FACTOR_NORMAL) {
             if (mLowRamStartTime == 0) {
                 mLowRamStartTime = now;
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 2249607..0ab81a5 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -91,6 +91,7 @@
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
+import android.util.AtomicFile;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.StatsEvent;
@@ -99,8 +100,10 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BinderCallsStats;
+import com.android.internal.os.Clock;
 import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.CpuScalingPolicyReader;
+import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.RailStats;
 import com.android.internal.os.RpmStats;
@@ -114,16 +117,21 @@
 import com.android.server.net.BaseNetworkObserver;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.power.optimization.Flags;
+import com.android.server.power.stats.AggregatedPowerStatsConfig;
 import com.android.server.power.stats.BatteryExternalStatsWorker;
 import com.android.server.power.stats.BatteryStatsImpl;
 import com.android.server.power.stats.BatteryUsageStatsProvider;
-import com.android.server.power.stats.BatteryUsageStatsStore;
+import com.android.server.power.stats.PowerStatsAggregator;
+import com.android.server.power.stats.PowerStatsScheduler;
+import com.android.server.power.stats.PowerStatsStore;
 import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 import com.android.server.power.stats.wakeups.CpuWakeupStats;
 
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.PrintWriter;
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
@@ -136,6 +144,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Properties;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
@@ -153,20 +162,24 @@
     static final String TAG = "BatteryStatsService";
     static final String TRACE_TRACK_WAKEUP_REASON = "wakeup_reason";
     static final boolean DBG = false;
-    private static final boolean BATTERY_USAGE_STORE_ENABLED = true;
 
     private static IBatteryStats sService;
 
     private final PowerProfile mPowerProfile;
     private final CpuScalingPolicies mCpuScalingPolicies;
+    private final MonotonicClock mMonotonicClock;
     private final BatteryStatsImpl.BatteryStatsConfig mBatteryStatsConfig;
     final BatteryStatsImpl mStats;
     final CpuWakeupStats mCpuWakeupStats;
-    private final BatteryUsageStatsStore mBatteryUsageStatsStore;
+    private final PowerStatsStore mPowerStatsStore;
+    private final PowerStatsAggregator mPowerStatsAggregator;
+    private final PowerStatsScheduler mPowerStatsScheduler;
     private final BatteryStatsImpl.UserInfoProvider mUserManagerUserInfoProvider;
     private final Context mContext;
     private final BatteryExternalStatsWorker mWorker;
     private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+    private final AtomicFile mConfigFile;
+
     private volatile boolean mMonitorEnabled = true;
 
     private native void getRailEnergyPowerStats(RailStats railStats);
@@ -376,6 +389,7 @@
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
 
+        mMonotonicClock = new MonotonicClock(new File(systemDir, "monotonic_clock.xml"));
         mPowerProfile = new PowerProfile(context);
         mCpuScalingPolicies = new CpuScalingPolicyReader().read();
 
@@ -391,23 +405,43 @@
                         .setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge)
                         .setPowerStatsThrottlePeriodCpu(powerStatsThrottlePeriodCpu)
                         .build();
-        mStats = new BatteryStatsImpl(mBatteryStatsConfig, systemDir, handler, this,
-                this, mUserManagerUserInfoProvider, mPowerProfile, mCpuScalingPolicies);
+        mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
+                systemDir, handler, this, this, mUserManagerUserInfoProvider, mPowerProfile,
+                mCpuScalingPolicies);
         mWorker = new BatteryExternalStatsWorker(context, mStats);
         mStats.setExternalStatsSyncLocked(mWorker);
         mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_radioScanningTimeout) * 1000L);
         mStats.startTrackingSystemServerCpuTime();
 
-        if (BATTERY_USAGE_STORE_ENABLED) {
-            mBatteryUsageStatsStore =
-                    new BatteryUsageStatsStore(context, mStats, systemDir, mHandler);
-        } else {
-            mBatteryUsageStatsStore = null;
-        }
+        AggregatedPowerStatsConfig aggregatedPowerStatsConfig = getAggregatedPowerStatsConfig();
+        mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, aggregatedPowerStatsConfig);
         mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats,
-                mBatteryUsageStatsStore);
+                mPowerStatsStore);
+        mPowerStatsAggregator = new PowerStatsAggregator(aggregatedPowerStatsConfig,
+                mStats.getHistory());
+        final long aggregatedPowerStatsSpanDuration = context.getResources().getInteger(
+                com.android.internal.R.integer.config_aggregatedPowerStatsSpanDuration);
+        final long powerStatsAggregationPeriod = context.getResources().getInteger(
+                com.android.internal.R.integer.config_powerStatsAggregationPeriod);
+        mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator,
+                aggregatedPowerStatsSpanDuration, powerStatsAggregationPeriod, mPowerStatsStore,
+                Clock.SYSTEM_CLOCK, mMonotonicClock, mHandler, mStats, mBatteryUsageStatsProvider);
         mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler);
+        mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config"));
+    }
+
+    private AggregatedPowerStatsConfig getAggregatedPowerStatsConfig() {
+        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
+        return config;
     }
 
     /**
@@ -466,9 +500,7 @@
      */
     public void onSystemReady() {
         mStats.onSystemReady();
-        if (BATTERY_USAGE_STORE_ENABLED) {
-            mBatteryUsageStatsStore.onSystemReady();
-        }
+        mPowerStatsScheduler.start(Flags.streamlinedBatteryStats());
     }
 
     private final class LocalService extends BatteryStatsInternal {
@@ -639,6 +671,9 @@
 
         // Shutdown the thread we made.
         mWorker.shutdown();
+
+        // To insure continuity, write the monotonic timeshift after writing the last history event
+        mMonotonicClock.write();
     }
 
     public static IBatteryStats getService() {
@@ -892,12 +927,8 @@
                     bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);
                     break;
                 case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET:
-                    if (!BATTERY_USAGE_STORE_ENABLED) {
-                        return StatsManager.PULL_SKIP;
-                    }
-
-                    final long sessionStart = mBatteryUsageStatsStore
-                            .getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
+                    final long sessionStart =
+                            getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
                     final long sessionEnd;
                     synchronized (mStats) {
                         sessionEnd = mStats.getStartClockTime();
@@ -910,8 +941,7 @@
                                     .aggregateSnapshots(sessionStart, sessionEnd)
                                     .build();
                     bus = getBatteryUsageStats(List.of(queryBeforeReset)).get(0);
-                    mBatteryUsageStatsStore
-                            .setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd);
+                    setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd);
                     break;
                 default:
                     throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
@@ -2641,7 +2671,15 @@
     }
 
     private void dumpAggregatedStats(PrintWriter pw) {
-        mStats.dumpAggregatedStats(pw, /* startTime */ 0, /* endTime */0);
+        mPowerStatsScheduler.aggregateAndDumpPowerStats(pw);
+    }
+
+    private void dumpPowerStatsStore(PrintWriter pw) {
+        mPowerStatsStore.dump(new IndentingPrintWriter(pw, "  "));
+    }
+
+    private void dumpPowerStatsStoreTableOfContents(PrintWriter pw) {
+        mPowerStatsStore.dumpTableOfContents(new IndentingPrintWriter(pw, "  "));
     }
 
     private void dumpMeasuredEnergyStats(PrintWriter pw) {
@@ -2789,7 +2827,7 @@
                     synchronized (mStats) {
                         mStats.resetAllStatsAndHistoryLocked(
                                 BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
-                        mBatteryUsageStatsStore.removeAllSnapshots();
+                        mPowerStatsStore.reset();
                         pw.println("Battery stats and history reset.");
                         noOutput = true;
                     }
@@ -2891,6 +2929,12 @@
                 } else if ("--aggregated".equals(arg)) {
                     dumpAggregatedStats(pw);
                     return;
+                } else if ("--store".equals(arg)) {
+                    dumpPowerStatsStore(pw);
+                    return;
+                } else if ("--store-toc".equals(arg)) {
+                    dumpPowerStatsStoreTableOfContents(pw);
+                    return;
                 } else if ("-a".equals(arg)) {
                     flags |= BatteryStats.DUMP_VERBOSE;
                 } else if (arg.length() > 0 && arg.charAt(0) == '-'){
@@ -2951,7 +2995,7 @@
                                 in.unmarshall(raw, 0, raw.length);
                                 in.setDataPosition(0);
                                 BatteryStatsImpl checkinStats = new BatteryStatsImpl(
-                                        mBatteryStatsConfig,
+                                        mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
                                         null, mStats.mHandler, null, null,
                                         mUserManagerUserInfoProvider, mPowerProfile,
                                         mCpuScalingPolicies);
@@ -2993,7 +3037,7 @@
                                 in.unmarshall(raw, 0, raw.length);
                                 in.setDataPosition(0);
                                 BatteryStatsImpl checkinStats = new BatteryStatsImpl(
-                                        mBatteryStatsConfig,
+                                        mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
                                         null, mStats.mHandler, null, null,
                                         mUserManagerUserInfoProvider, mPowerProfile,
                                         mCpuScalingPolicies);
@@ -3360,6 +3404,52 @@
         }
     }
 
+    private static final String BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY =
+            "BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP";
+
+    /**
+     * Saves the supplied timestamp of the BATTERY_USAGE_STATS_BEFORE_RESET statsd atom pull
+     * in persistent file.
+     */
+    public void setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(long timestamp) {
+        synchronized (mConfigFile) {
+            Properties props = new Properties();
+            try (InputStream in = mConfigFile.openRead()) {
+                props.load(in);
+            } catch (IOException e) {
+                Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
+            }
+            props.put(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY,
+                    String.valueOf(timestamp));
+            FileOutputStream out = null;
+            try {
+                out = mConfigFile.startWrite();
+                props.store(out, "Statsd atom pull timestamps");
+                mConfigFile.finishWrite(out);
+            } catch (IOException e) {
+                mConfigFile.failWrite(out);
+                Slog.e(TAG, "Cannot save config file " + mConfigFile, e);
+            }
+        }
+    }
+
+    /**
+     * Retrieves the previously saved timestamp of the last BATTERY_USAGE_STATS_BEFORE_RESET
+     * statsd atom pull.
+     */
+    public long getLastBatteryUsageStatsBeforeResetAtomPullTimestamp() {
+        synchronized (mConfigFile) {
+            Properties props = new Properties();
+            try (InputStream in = mConfigFile.openRead()) {
+                props.load(in);
+            } catch (IOException e) {
+                Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
+            }
+            return Long.parseLong(
+                    props.getProperty(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, "0"));
+        }
+    }
+
     /**
      * Sets battery AC charger to enabled/disabled, and freezes the battery state.
      */
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/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 4b622f5..903cb7b 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -175,13 +175,15 @@
                 TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT));
 
         // Register all text aconfig flags.
-        for (String flag : TextFlags.TEXT_ACONFIGS_FLAGS) {
+        for (int i = 0; i < TextFlags.TEXT_ACONFIGS_FLAGS.length; i++) {
+            final String flag = TextFlags.TEXT_ACONFIGS_FLAGS[i];
+            final boolean defaultValue = TextFlags.TEXT_ACONFIG_DEFAULT_VALUE[i];
             sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
                     TextFlags.NAMESPACE,
                     flag,
                     TextFlags.getKeyForFlag(flag),
                     boolean.class,
-                    false));  // All aconfig flags are false by default.
+                    defaultValue));
         }
         // add other device configs here...
     }
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 db74f1a..940c58b 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -142,6 +142,11 @@
     final AtomicLong mCurCpuTime = new AtomicLong(0);
 
     /**
+     * How long the process has spent on waiting in the runqueue since fork.
+     */
+    final AtomicLong mLastCpuDelayTime = new AtomicLong(0);
+
+    /**
      * Last selected memory trimming level.
      */
     @CompositeRWLock({"mService", "mProcLock"})
@@ -570,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/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index d8a2695..3771c05 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1255,11 +1255,10 @@
             killProcessGroup = true;
         }
         if (killProcessGroup) {
-            if (async) {
-                ProcessList.killProcessGroup(uid, mPid);
-            } else {
+            if (!async) {
                 Process.sendSignalToProcessGroup(uid, mPid, OsConstants.SIGKILL);
             }
+            ProcessList.killProcessGroup(uid, mPid);
         }
     }
 
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/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
index 241abaf..85acf70 100644
--- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
+++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
@@ -47,6 +47,12 @@
                 return setEncodedSurroundMode();
             case "get-encoded-surround-mode":
                 return getEncodedSurroundMode();
+            case "set-sound-dose-value":
+                return setSoundDoseValue();
+            case "get-sound-dose-value":
+                return getSoundDoseValue();
+            case "reset-sound-dose-timeout":
+                return resetSoundDoseTimeout();
         }
         return 0;
     }
@@ -66,6 +72,12 @@
         pw.println("    Sets the encoded surround sound mode to SURROUND_SOUND_MODE");
         pw.println("  get-encoded-surround-mode");
         pw.println("    Returns the encoded surround sound mode");
+        pw.println("  set-sound-dose-value");
+        pw.println("    Sets the current sound dose value");
+        pw.println("  get-sound-dose-value");
+        pw.println("    Returns the current sound dose value");
+        pw.println("  reset-sound-dose-timeout");
+        pw.println("    Resets the sound dose timeout used for momentary exposure");
     }
 
     private int setSurroundFormatEnabled() {
@@ -162,4 +174,46 @@
         getOutPrintWriter().println("Encoded surround mode: " + am.getEncodedSurroundMode());
         return 0;
     }
+
+    private int setSoundDoseValue() {
+        String soundDoseValueText = getNextArg();
+
+        if (soundDoseValueText == null) {
+            getErrPrintWriter().println("Error: no sound dose value specified");
+            return 1;
+        }
+
+        float soundDoseValue = 0.f;
+        try {
+            soundDoseValue = Float.parseFloat(soundDoseValueText);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: wrong format specified for sound dose");
+            return 1;
+        }
+
+        if (soundDoseValue < 0) {
+            getErrPrintWriter().println("Error: invalid value of sound dose");
+            return 1;
+        }
+
+        final Context context = mService.mContext;
+        final AudioManager am = context.getSystemService(AudioManager.class);
+        am.setCsd(soundDoseValue);
+        return 0;
+    }
+
+    private int getSoundDoseValue() {
+        final Context context = mService.mContext;
+        final AudioManager am = context.getSystemService(AudioManager.class);
+        getOutPrintWriter().println("Sound dose value: " + am.getCsd());
+        return 0;
+    }
+
+    private int resetSoundDoseTimeout() {
+        final Context context = mService.mContext;
+        final AudioManager am = context.getSystemService(AudioManager.class);
+        am.setCsd(-1.f);
+        getOutPrintWriter().println("Reset sound dose momentary exposure timeout");
+        return 0;
+    }
 }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0aa9cc1..3243385 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -36,7 +36,6 @@
 import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE;
 import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
 import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
-
 import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
 import static com.android.server.utils.EventLogger.Event.ALOGE;
 import static com.android.server.utils.EventLogger.Event.ALOGI;
@@ -73,7 +72,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -267,6 +265,8 @@
     private final SettingsAdapter mSettings;
     private final AudioPolicyFacade mAudioPolicy;
 
+    private final MusicFxHelper mMusicFxHelper;
+
     /** Debug audio mode */
     protected static final boolean DEBUG_MODE = false;
 
@@ -407,9 +407,15 @@
     private static final int MSG_CONFIGURATION_CHANGED = 54;
     private static final int MSG_BROADCAST_MASTER_MUTE = 55;
 
-    /** Messages handled by the {@link SoundDoseHelper}. */
+    /**
+     * Messages handled by the {@link SoundDoseHelper}, do not exceed
+     * {@link MUSICFX_HELPER_MSG_START}.
+     */
     /*package*/ static final int SAFE_MEDIA_VOLUME_MSG_START = 1000;
 
+    /** Messages handled by the {@link MusicFxHelper}. */
+    /*package*/ static final int MUSICFX_HELPER_MSG_START = 1100;
+
     // start of messages handled under wakelock
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
     //   and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
@@ -1304,6 +1310,8 @@
                 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
 
         mDisplayManager = context.getSystemService(DisplayManager.class);
+
+        mMusicFxHelper = new MusicFxHelper(mContext, mAudioHandler);
     }
 
     private void initVolumeStreamStates() {
@@ -9456,11 +9464,21 @@
                     onConfigurationChanged();
                     break;
 
+                case MusicFxHelper.MSG_EFFECT_CLIENT_GONE:
+                    mMusicFxHelper.handleMessage(msg);
+                    break;
+
+                case SoundDoseHelper.MSG_CONFIGURE_SAFE_MEDIA:
+                case SoundDoseHelper.MSG_CONFIGURE_SAFE_MEDIA_FORCED:
+                case SoundDoseHelper.MSG_PERSIST_SAFE_VOLUME_STATE:
+                case SoundDoseHelper.MSG_PERSIST_MUSIC_ACTIVE_MS:
+                case SoundDoseHelper.MSG_PERSIST_CSD_VALUES:
+                case SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION:
+                    mSoundDoseHelper.handleMessage(msg);
+                    break;
+
                 default:
-                    if (msg.what >= SAFE_MEDIA_VOLUME_MSG_START) {
-                        // msg could be for the SoundDoseHelper
-                        mSoundDoseHelper.handleMessage(msg);
-                    }
+                    Log.e(TAG, "Unsupported msgId " + msg.what);
             }
         }
     }
@@ -9695,7 +9713,7 @@
                 }
             } else if (action.equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION) ||
                     action.equals(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)) {
-                handleAudioEffectBroadcast(context, intent);
+                mMusicFxHelper.handleAudioEffectBroadcast(context, intent);
             } else if (action.equals(Intent.ACTION_PACKAGES_SUSPENDED)) {
                 final int[] suspendedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
                 final String[] suspendedPackages =
@@ -9750,27 +9768,6 @@
         }
     } // end class AudioServiceUserRestrictionsListener
 
-    private void handleAudioEffectBroadcast(Context context, Intent intent) {
-        String target = intent.getPackage();
-        if (target != null) {
-            Log.w(TAG, "effect broadcast already targeted to " + target);
-            return;
-        }
-        intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
-        // TODO this should target a user-selected panel
-        List<ResolveInfo> ril = context.getPackageManager().queryBroadcastReceivers(
-                intent, 0 /* flags */);
-        if (ril != null && ril.size() != 0) {
-            ResolveInfo ri = ril.get(0);
-            if (ri != null && ri.activityInfo != null && ri.activityInfo.packageName != null) {
-                intent.setPackage(ri.activityInfo.packageName);
-                context.sendBroadcastAsUser(intent, UserHandle.ALL);
-                return;
-            }
-        }
-        Log.w(TAG, "couldn't find receiver package for effect intent");
-    }
-
     private void killBackgroundUserProcessesWithRecordAudioPermission(UserInfo oldUser) {
         PackageManager pm = mContext.getPackageManager();
         // Find the home activity of the user. It should not be killed to avoid expensive restart,
diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java
new file mode 100644
index 0000000..6c0fef5
--- /dev/null
+++ b/services/core/java/com/android/server/audio/MusicFxHelper.java
@@ -0,0 +1,295 @@
+/*
+ * 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.audio;
+
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static com.android.server.audio.AudioService.MUSICFX_HELPER_MSG_START;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.IUidObserver;
+import android.app.UidObserver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.media.AudioManager;
+import android.media.audiofx.AudioEffect;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.audio.AudioService.AudioHandler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * MusicFx management.
+ * .
+ */
+public class MusicFxHelper {
+    private static final String TAG = "AS.MusicFxHelper";
+
+    @NonNull private final Context mContext;
+
+    @NonNull private final AudioHandler mAudioHandler;
+
+    // Synchronization UidSessionMap access between UidObserver and AudioServiceBroadcastReceiver.
+    private final Object mClientUidMapLock = new Object();
+
+    // The binder token identifying the UidObserver registration.
+    private IBinder mUidObserverToken = null;
+
+    // Hashmap of UID and list of open sessions for this UID.
+    @GuardedBy("mClientUidMapLock")
+    private SparseArray<List<Integer>> mClientUidSessionMap = new SparseArray<>();
+
+    /*package*/ static final int MSG_EFFECT_CLIENT_GONE = MUSICFX_HELPER_MSG_START + 1;
+
+    // UID observer for effect MusicFx clients
+    private final IUidObserver mEffectUidObserver = new UidObserver() {
+        @Override public void onUidGone(int uid, boolean disabled) {
+            Log.w(TAG, " send MSG_EFFECT_CLIENT_GONE");
+            mAudioHandler.sendMessageAtTime(
+                    mAudioHandler.obtainMessage(MSG_EFFECT_CLIENT_GONE,
+                            uid /* arg1 */, 0 /* arg2 */,
+                            null /* obj */), 0 /* delay */);
+        }
+    };
+
+    // BindService connection implementation, we don't need any implementation now
+    private ServiceConnection mMusicFxBindConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            Log.d(TAG, " service connected to " + name);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            Log.d(TAG, " service disconnected from " + name);
+        }
+    };
+
+    MusicFxHelper(@NonNull Context context, @NonNull AudioHandler audioHandler) {
+        mContext = context;
+        mAudioHandler = audioHandler;
+    }
+
+    /**
+     * Handle the broadcast {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} and
+     * {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} intents.
+     *
+     * If the intent is {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION}:
+     *  - If the MusicFx process is not running, call bindService with AUTO_CREATE to create.
+     *  - If this is the first audio session in MusicFx, call set foreground service delegate.
+     *  - If this is the first audio session for a given UID, add the UID into observer.
+     *
+     * If the intent is {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION}:
+     *  - MusicFx will not be foreground delegated anymore.
+     *  - The KeepAliveService of MusicFx will be unbound.
+     *  - The UidObserver will be removed.
+     */
+    public void handleAudioEffectBroadcast(Context context, Intent intent) {
+        String target = intent.getPackage();
+        if (target != null) {
+            Log.w(TAG, "effect broadcast already targeted to " + target);
+            return;
+        }
+        intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+        final PackageManager pm = context.getPackageManager();
+        // TODO this should target a user-selected panel
+        List<ResolveInfo> ril = pm.queryBroadcastReceivers(intent, 0 /* flags */);
+        if (ril != null && ril.size() != 0) {
+            ResolveInfo ri = ril.get(0);
+            final String senderPackageName = intent.getStringExtra(AudioEffect.EXTRA_PACKAGE_NAME);
+            try {
+                final int senderUid = pm.getPackageUidAsUser(senderPackageName,
+                        PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId());
+                if (ri != null && ri.activityInfo != null && ri.activityInfo.packageName != null) {
+                    intent.setPackage(ri.activityInfo.packageName);
+                    synchronized (mClientUidMapLock) {
+                        setMusicFxServiceWithObserver(context, intent, senderUid);
+                    }
+                    context.sendBroadcastAsUser(intent, UserHandle.ALL);
+                    return;
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.e(TAG, "Not able to find UID from package: " + senderPackageName + " error: "
+                        + e);
+            }
+        }
+        Log.w(TAG, "couldn't find receiver package for effect intent");
+    }
+
+    /**
+     * Handle the UidObserver onUidGone callback of MusicFx clients.
+     * All open audio sessions of this UID will be closed.
+     * If this is the last UID for MusicFx:
+     *  - MusicFx will not be foreground delegated anymore.
+     *  - The KeepAliveService of MusicFx will be unbound.
+     *  - The UidObserver will be removed.
+     */
+    public void handleEffectClientUidGone(int uid) {
+        synchronized (mClientUidMapLock) {
+            Log.w(TAG, " inside handle MSG_EFFECT_CLIENT_GONE");
+            // Once the uid is no longer running, close all remain audio session(s) for this UID
+            if (mClientUidSessionMap.get(Integer.valueOf(uid)) != null) {
+                final List<Integer> sessions = mClientUidSessionMap.get(Integer.valueOf(uid));
+                Log.i(TAG, "UID " + uid + " gone, closing " + sessions.size() + " sessions");
+                for (Integer session : sessions) {
+                    Intent intent = new Intent(
+                            AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
+                    intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, session);
+                    setMusicFxServiceWithObserver(mContext, intent, uid);
+                    Log.i(TAG, "Close session " + session + " of UID " + uid);
+                }
+                mClientUidSessionMap.remove(Integer.valueOf(uid));
+            }
+        }
+    }
+
+    @GuardedBy("mClientUidMapLock")
+    private void setMusicFxServiceWithObserver(Context context, Intent intent, int senderUid) {
+        PackageManager pm = context.getPackageManager();
+        try {
+            final int audioSession = intent.getIntExtra(AudioEffect.EXTRA_AUDIO_SESSION,
+                    AudioManager.AUDIO_SESSION_ID_GENERATE);
+            if (AudioManager.AUDIO_SESSION_ID_GENERATE == audioSession) {
+                Log.e(TAG, "Intent missing audio session: " + audioSession);
+                return;
+            }
+
+            // only apply to com.android.musicfx and KeepAliveService for now
+            final String musicFxPackageName = "com.android.musicfx";
+            final String musicFxKeepAliveService = "com.android.musicfx.KeepAliveService";
+            final int musicFxUid = pm.getPackageUidAsUser(musicFxPackageName,
+                    PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId());
+
+            if (intent.getAction().equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION)) {
+                List<Integer> sessions = new ArrayList<>();
+                Log.d(TAG, "UID " + senderUid + ", open MusicFx session " + audioSession);
+                // start foreground service delegate and register UID observer with the first
+                // session of first UID open
+                if (0 == mClientUidSessionMap.size()) {
+                    final int procState = ActivityManager.getService().getPackageProcessState(
+                            musicFxPackageName, this.getClass().getPackage().getName());
+                    // if musicfx process not in binding state, call bindService with AUTO_CREATE
+                    if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                        Intent bindIntent = new Intent().setClassName(musicFxPackageName,
+                                musicFxKeepAliveService);
+                        context.bindServiceAsUser(
+                                bindIntent, mMusicFxBindConnection, Context.BIND_AUTO_CREATE,
+                                UserHandle.of(getCurrentUserId()));
+                        Log.i(TAG, "bindService to " + musicFxPackageName);
+                    }
+
+                    Log.i(TAG, "Package " + musicFxPackageName + " uid " + musicFxUid
+                            + " procState " + procState);
+                } else if (mClientUidSessionMap.get(Integer.valueOf(senderUid)) != null) {
+                    sessions = mClientUidSessionMap.get(Integer.valueOf(senderUid));
+                    if (sessions.contains(audioSession)) {
+                        Log.e(TAG, "Audio session " + audioSession + " already exist for UID "
+                                + senderUid + ", abort");
+                        return;
+                    }
+                }
+                // first session of this UID
+                if (sessions.size() == 0) {
+                    // call registerUidObserverForUids with the first UID and first session
+                    if (mClientUidSessionMap.size() == 0 || mUidObserverToken == null) {
+                        mUidObserverToken = ActivityManager.getService().registerUidObserverForUids(
+                                mEffectUidObserver, ActivityManager.UID_OBSERVER_GONE,
+                                ActivityManager.PROCESS_STATE_UNKNOWN, null, new int[]{senderUid});
+                        Log.i(TAG, "UID " + senderUid + " registered to observer");
+                    } else {
+                        // add UID to observer for each new UID
+                        ActivityManager.getService().addUidToObserver(mUidObserverToken, TAG,
+                                senderUid);
+                        Log.i(TAG, "UID " + senderUid + " addeded to observer");
+                    }
+                }
+
+                sessions.add(Integer.valueOf(audioSession));
+                mClientUidSessionMap.put(Integer.valueOf(senderUid), sessions);
+            } else {
+                if (mClientUidSessionMap.get(senderUid) != null) {
+                    Log.d(TAG, "UID " + senderUid + ", close MusicFx session " + audioSession);
+                    List<Integer> sessions = mClientUidSessionMap.get(Integer.valueOf(senderUid));
+                    sessions.remove(Integer.valueOf(audioSession));
+                    if (0 == sessions.size()) {
+                        mClientUidSessionMap.remove(Integer.valueOf(senderUid));
+                    } else {
+                        mClientUidSessionMap.put(Integer.valueOf(senderUid), sessions);
+                    }
+
+                    // stop foreground service delegate and unregister UID observer with the
+                    // last session of last UID close
+                    if (0 == mClientUidSessionMap.size()) {
+                        ActivityManager.getService().unregisterUidObserver(mEffectUidObserver);
+                        mClientUidSessionMap.clear();
+                        context.unbindService(mMusicFxBindConnection);
+                        Log.i(TAG, " remove all sessions, unregister UID observer, and unbind "
+                                + musicFxPackageName);
+                    }
+                } else {
+                    // if the audio session already closed, print an error
+                    Log.e(TAG, "UID " + senderUid + " close audio session " + audioSession
+                            + " which does not exist");
+                }
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Not able to find UID from package: " + e);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException " + e + " with handling intent");
+        }
+    }
+
+    private int getCurrentUserId() {
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            UserInfo currentUser = ActivityManager.getService().getCurrentUser();
+            return currentUser.id;
+        } catch (RemoteException e) {
+            // Activity manager not running, nothing we can do assume user 0.
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        return UserHandle.USER_SYSTEM;
+    }
+
+    /*package*/ void handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_EFFECT_CLIENT_GONE:
+                Log.w(TAG, " handle MSG_EFFECT_CLIENT_GONE");
+                handleEffectClientUidGone(msg.arg1 /* uid */);
+                break;
+            default:
+                Log.e(TAG, "Unexpected msg to handle in MusicFxHelper: " + msg.what);
+                break;
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 81365bf..d65c7c2c 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -108,11 +108,11 @@
     private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2;  // confirmed
     private static final int SAFE_MEDIA_VOLUME_ACTIVE = 3;  // unconfirmed
 
-    private static final int MSG_CONFIGURE_SAFE_MEDIA = SAFE_MEDIA_VOLUME_MSG_START + 1;
-    private static final int MSG_CONFIGURE_SAFE_MEDIA_FORCED = SAFE_MEDIA_VOLUME_MSG_START + 2;
-    private static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 3;
-    private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 4;
-    private static final int MSG_PERSIST_CSD_VALUES = SAFE_MEDIA_VOLUME_MSG_START + 5;
+    /*package*/ static final int MSG_CONFIGURE_SAFE_MEDIA = SAFE_MEDIA_VOLUME_MSG_START + 1;
+    /*package*/ static final int MSG_CONFIGURE_SAFE_MEDIA_FORCED = SAFE_MEDIA_VOLUME_MSG_START + 2;
+    /*package*/ static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 3;
+    /*package*/ static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 4;
+    /*package*/ static final int MSG_PERSIST_CSD_VALUES = SAFE_MEDIA_VOLUME_MSG_START + 5;
     /*package*/ static final int MSG_CSD_UPDATE_ATTENUATION = SAFE_MEDIA_VOLUME_MSG_START + 6;
 
     private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
@@ -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 f74b45c..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
@@ -875,6 +876,10 @@
     public void simulateVhalFingerDown(int userId, int sensorId) {
         Slog.d(getTag(), "Simulate virtual HAL finger down event");
         final AidlSession session = mFingerprintSensors.get(sensorId).getSessionForUser(userId);
+        if (session == null) {
+            Slog.e(getTag(), "no existing hal session found - aborting");
+            return;
+        }
         final PointerContext pc = new PointerContext();
         try {
             session.getSession().onPointerDownWithContext(pc);
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/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index 472c1f5..1ac3a12 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -135,7 +135,8 @@
                             leadDisplayAddress,
                             d.getBrightnessThrottlingMapId(),
                             d.getRefreshRateZoneId(),
-                            d.getRefreshRateThermalThrottlingMapId());
+                            d.getRefreshRateThermalThrottlingMapId(),
+                            d.getPowerThrottlingMapId());
                 }
                 layout.postProcessLocked();
             }
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 098cb87..9f4f787 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -23,7 +23,7 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
 import android.hardware.display.DisplayViewport;
 import android.os.IBinder;
 import android.util.Slog;
@@ -201,20 +201,6 @@
      * @param state The new display state.
      * @param brightnessState The new display brightnessState.
      * @param sdrBrightnessState The new display brightnessState for SDR layers.
-     * @return A runnable containing work to be deferred until after we have
-     * exited the critical section, or null if none.
-     */
-    public Runnable requestDisplayStateLocked(int state, float brightnessState,
-            float sdrBrightnessState) {
-        return requestDisplayStateLocked(state, brightnessState, sdrBrightnessState, null);
-    }
-
-    /**
-     * Sets the display state, if supported.
-     *
-     * @param state The new display state.
-     * @param brightnessState The new display brightnessState.
-     * @param sdrBrightnessState The new display brightnessState for SDR layers.
      * @param displayOffloadSession {@link DisplayOffloadSession} associated with current device.
      * @return A runnable containing work to be deferred until after we have exited the critical
      *     section, or null if none.
@@ -223,7 +209,7 @@
             int state,
             float brightnessState,
             float sdrBrightnessState,
-            @Nullable DisplayManagerInternal.DisplayOffloadSession displayOffloadSession) {
+            @Nullable DisplayOffloadSession displayOffloadSession) {
         return null;
     }
 
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..d97c8e7 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -30,6 +30,8 @@
 
 class DisplayManagerShellCommand extends ShellCommand {
     private static final String TAG = "DisplayManagerShellCommand";
+    private static final String NOTIFICATION_TYPES =
+            "on-hotplug-error, on-link-training-failure, on-cable-dp-incapable";
 
     private final DisplayManagerService mService;
     private final DisplayManagerFlags mFlags;
@@ -46,6 +48,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 +108,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 +182,39 @@
         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;
+            case "on-link-training-failure":
+                mService.getDisplayNotificationManager().onDisplayPortLinkTrainingFailure();
+                break;
+            case "on-cable-dp-incapable":
+                mService.getDisplayNotificationManager().onCableNotCapableDisplayPort();
+                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/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 1652871..7d9c018 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -506,7 +506,6 @@
         mTag = "DisplayPowerController2[" + mDisplayId + "]";
         mThermalBrightnessThrottlingDataId =
                 logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
-
         mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
         mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
         mDisplayStatsId = mUniqueDisplayId.hashCode();
@@ -566,8 +565,8 @@
                 modeChangeCallback::run, new BrightnessClamperController.DisplayDeviceData(
                 mUniqueDisplayId,
                 mThermalBrightnessThrottlingDataId,
-                mDisplayDeviceConfig
-        ), mContext);
+                logicalDisplay.getPowerThrottlingDataIdLocked(),
+                mDisplayDeviceConfig), mContext, flags);
         // Seed the cached brightness
         saveBrightnessInfo(getScreenBrightnessSetting());
         mAutomaticBrightnessStrategy =
@@ -821,10 +820,8 @@
                 .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL;
         final String thermalBrightnessThrottlingDataId =
                 mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
-
-        mBrightnessClamperController.onDisplayChanged(
-                new BrightnessClamperController.DisplayDeviceData(mUniqueDisplayId,
-                        mThermalBrightnessThrottlingDataId, config));
+        final String powerThrottlingDataId =
+                mLogicalDisplay.getPowerThrottlingDataIdLocked();
 
         mHandler.postAtTime(() -> {
             boolean changed = false;
@@ -858,6 +855,14 @@
             }
 
             mIsDisplayInternal = isDisplayInternal;
+            // using local variables here, when mBrightnessThrottler is removed,
+            // mThermalBrightnessThrottlingDataId could be removed as well
+            // changed = true will be not needed - clampers are maintaining their state and
+            // will call updatePowerState if needed.
+            mBrightnessClamperController.onDisplayChanged(
+                    new BrightnessClamperController.DisplayDeviceData(uniqueId,
+                        thermalBrightnessThrottlingDataId, powerThrottlingDataId, config));
+
             if (changed) {
                 updatePowerState();
             }
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/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index bd82b81..3d4209e 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -190,6 +190,11 @@
     private SurfaceControl.RefreshRateRange mLayoutLimitedRefreshRate;
 
     /**
+     * The ID of the power throttling data that should be used.
+     */
+    private String mPowerThrottlingDataId;
+
+    /**
      * RefreshRateRange limitation for @Temperature.ThrottlingStatus
      */
     @NonNull
@@ -205,6 +210,7 @@
         mIsEnabled = true;
         mIsInTransition = false;
         mThermalBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
+        mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
         mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId;
     }
 
@@ -911,6 +917,25 @@
     }
 
     /**
+     * @param powerThrottlingDataId The ID of the brightness throttling data that this
+     *                                  display should use.
+     */
+    public void setPowerThrottlingDataIdLocked(String powerThrottlingDataId) {
+        if (!Objects.equals(powerThrottlingDataId, mPowerThrottlingDataId)) {
+            mPowerThrottlingDataId = powerThrottlingDataId;
+            mDirty = true;
+        }
+    }
+
+    /**
+     * Returns powerThrottlingDataId which is the ID of the brightness
+     * throttling data that this display should use.
+     */
+    public String getPowerThrottlingDataIdLocked() {
+        return mPowerThrottlingDataId;
+    }
+
+    /**
      * Sets the display of which this display is a follower, regarding brightness or other
      * properties. If set to {@link Layout#NO_LEAD_DISPLAY}, this display does not follow any
      * others, and has the potential to be a lead display to others.
@@ -976,6 +1001,7 @@
         pw.println("mLeadDisplayId=" + mLeadDisplayId);
         pw.println("mLayoutLimitedRefreshRate=" + mLayoutLimitedRefreshRate);
         pw.println("mThermalRefreshRateThrottling=" + mThermalRefreshRateThrottling);
+        pw.println("mPowerThrottlingDataId=" + mPowerThrottlingDataId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index b3b16ad..c55bc62 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -1123,13 +1123,15 @@
                             displayLayout.getRefreshRateThermalThrottlingMapId()
                     )
             );
-
             setEnabledLocked(newDisplay, displayLayout.isEnabled());
             newDisplay.setThermalBrightnessThrottlingDataIdLocked(
                     displayLayout.getThermalBrightnessThrottlingMapId() == null
                             ? DisplayDeviceConfig.DEFAULT_ID
                             : displayLayout.getThermalBrightnessThrottlingMapId());
-
+            newDisplay.setPowerThrottlingDataIdLocked(
+                    displayLayout.getPowerThrottlingMapId() == null
+                            ? DisplayDeviceConfig.DEFAULT_ID
+                            : displayLayout.getPowerThrottlingMapId());
             newDisplay.setDisplayGroupNameLocked(displayLayout.getDisplayGroupName());
         }
     }
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index d910e16..b002587 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -43,6 +43,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Point;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.display.VirtualDisplayConfig;
 import android.media.projection.IMediaProjection;
@@ -395,7 +396,7 @@
 
         @Override
         public Runnable requestDisplayStateLocked(int state, float brightnessState,
-                float sdrBrightnessState) {
+                float sdrBrightnessState, DisplayOffloadSession displayOffloadSession) {
             if (state != mDisplayState) {
                 mDisplayState = state;
                 if (state == Display.STATE_OFF) {
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index 54a280f..68f72d3 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -52,7 +52,8 @@
 
     abstract void stop();
 
-    enum Type {
-        THERMAL
+    protected enum Type {
+        THERMAL,
+        POWER
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 14637af..787f786 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -34,9 +34,12 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
 import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -48,11 +51,9 @@
  */
 public class BrightnessClamperController {
     private static final String TAG = "BrightnessClamperController";
-
     private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
     private final Handler mHandler;
     private final ClamperChangeListener mClamperChangeListenerExternal;
-
     private final Executor mExecutor;
     private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers;
 
@@ -64,13 +65,15 @@
     private boolean mClamperApplied = false;
 
     public BrightnessClamperController(Handler handler,
-            ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context) {
-        this(new Injector(), handler, clamperChangeListener, data, context);
+            ClamperChangeListener clamperChangeListener, DisplayDeviceData data,  Context context,
+            DisplayManagerFlags flags) {
+        this(new Injector(), handler, clamperChangeListener, data, context, flags);
     }
 
     @VisibleForTesting
     BrightnessClamperController(Injector injector, Handler handler,
-            ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context) {
+            ClamperChangeListener clamperChangeListener, DisplayDeviceData data,  Context context,
+            DisplayManagerFlags flags) {
         mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
         mHandler = handler;
         mClamperChangeListenerExternal = clamperChangeListener;
@@ -84,7 +87,7 @@
             }
         };
 
-        mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data);
+        mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags);
         mModifiers = injector.getModifiers(context);
         mOnPropertiesChangedListener =
                 properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
@@ -144,6 +147,8 @@
             return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
         } else if (mClamperType == Type.THERMAL) {
             return BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
+        } else if (mClamperType == Type.POWER) {
+            return BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC;
         } else {
             Slog.wtf(TAG, "BrightnessMaxReason not mapped for type=" + mClamperType);
             return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
@@ -193,6 +198,7 @@
             mClamperType = clamperType;
             mClamperChangeListenerExternal.onChanged();
         }
+
     }
 
     private void start() {
@@ -219,10 +225,15 @@
         }
 
         List<BrightnessClamper<? super DisplayDeviceData>> getClampers(Handler handler,
-                ClamperChangeListener clamperChangeListener, DisplayDeviceData data) {
+                ClamperChangeListener clamperChangeListener, DisplayDeviceData data,
+                DisplayManagerFlags flags) {
             List<BrightnessClamper<? super DisplayDeviceData>> clampers = new ArrayList<>();
             clampers.add(
                     new BrightnessThermalClamper(handler, clamperChangeListener, data));
+            if (flags.isPowerThrottlingClamperEnabled()) {
+                clampers.add(new BrightnessPowerClamper(handler, clamperChangeListener,
+                            data));
+            }
             return clampers;
         }
 
@@ -235,21 +246,26 @@
     }
 
     /**
-     * Data for clampers
+     * Config Data for clampers
      */
-    public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData {
+    public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData,
+                BrightnessPowerClamper.PowerData {
         @NonNull
         private final String mUniqueDisplayId;
         @NonNull
         private final String mThermalThrottlingDataId;
+        @NonNull
+        private final String mPowerThrottlingDataId;
 
         private final DisplayDeviceConfig mDisplayDeviceConfig;
 
         public DisplayDeviceData(@NonNull String uniqueDisplayId,
                 @NonNull String thermalThrottlingDataId,
+                @NonNull String powerThrottlingDataId,
                 @NonNull DisplayDeviceConfig displayDeviceConfig) {
             mUniqueDisplayId = uniqueDisplayId;
             mThermalThrottlingDataId = thermalThrottlingDataId;
+            mPowerThrottlingDataId = powerThrottlingDataId;
             mDisplayDeviceConfig = displayDeviceConfig;
         }
 
@@ -272,5 +288,24 @@
             return mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId().get(
                     mThermalThrottlingDataId);
         }
+
+        @NonNull
+        @Override
+        public String getPowerThrottlingDataId() {
+            return mPowerThrottlingDataId;
+        }
+
+        @Nullable
+        @Override
+        public PowerThrottlingData getPowerThrottlingData() {
+            return mDisplayDeviceConfig.getPowerThrottlingDataMapByThrottlingId().get(
+                    mPowerThrottlingDataId);
+        }
+
+        @Nullable
+        @Override
+        public PowerThrottlingConfigData getPowerThrottlingConfigData() {
+            return mDisplayDeviceConfig.getPowerThrottlingConfigData();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
new file mode 100644
index 0000000..339b589
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
@@ -0,0 +1,295 @@
+/*
+ * 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.brightness.clamper;
+
+import static com.android.server.display.DisplayDeviceConfig.DEFAULT_ID;
+import static com.android.server.display.brightness.clamper.BrightnessClamperController.ClamperChangeListener;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.Temperature;
+import android.provider.DeviceConfigInterface;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.utils.DeviceConfigParsingUtils;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+
+class BrightnessPowerClamper extends
+        BrightnessClamper<BrightnessPowerClamper.PowerData> {
+
+    private static final String TAG = "BrightnessPowerClamper";
+    @NonNull
+    private final Injector mInjector;
+    @NonNull
+    private final DeviceConfigParameterProvider mConfigParameterProvider;
+    @NonNull
+    private final Handler mHandler;
+    @NonNull
+    private final ClamperChangeListener mChangeListener;
+    @Nullable
+    private PmicMonitor mPmicMonitor;
+    // data from DeviceConfig, for all displays, for all dataSets
+    // mapOf(uniqueDisplayId to mapOf(dataSetId to PowerThrottlingData))
+    @NonNull
+    private Map<String, Map<String, PowerThrottlingData>>
+            mPowerThrottlingDataOverride = Map.of();
+    // data from DisplayDeviceConfig, for particular display+dataSet
+    @Nullable
+    private PowerThrottlingData mPowerThrottlingDataFromDDC = null;
+    // Active data, if mPowerThrottlingDataOverride contains data for mUniqueDisplayId,
+    // mDataId, then use it, otherwise mPowerThrottlingDataFromDDC.
+    @Nullable
+    private PowerThrottlingData mPowerThrottlingDataActive = null;
+    @Nullable
+    private PowerThrottlingConfigData mPowerThrottlingConfigData = null;
+
+    private @Temperature.ThrottlingStatus int mCurrentThermalLevel = Temperature.THROTTLING_NONE;
+    private float mCurrentAvgPowerConsumed = 0;
+    @Nullable
+    private String mUniqueDisplayId = null;
+    @Nullable
+    private String mDataId = null;
+
+    private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
+        try {
+            int status = DeviceConfigParsingUtils.parseThermalStatus(key);
+            float powerQuota = Float.parseFloat(value);
+            return new ThrottlingLevel(status, powerQuota);
+        } catch (IllegalArgumentException iae) {
+            return null;
+        }
+    };
+
+    private final Function<List<ThrottlingLevel>, PowerThrottlingData>
+            mDataSetMapper = PowerThrottlingData::create;
+
+
+    BrightnessPowerClamper(Handler handler, ClamperChangeListener listener,
+            PowerData powerData) {
+        this(new Injector(), handler, listener, powerData);
+    }
+
+    @VisibleForTesting
+    BrightnessPowerClamper(Injector injector, Handler handler, ClamperChangeListener listener,
+            PowerData powerData) {
+        mInjector = injector;
+        mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
+        mHandler = handler;
+        mChangeListener = listener;
+
+        mHandler.post(() -> {
+            setDisplayData(powerData);
+            loadOverrideData();
+            start();
+        });
+
+    }
+
+    @Override
+    @NonNull
+    BrightnessClamper.Type getType() {
+        return Type.POWER;
+    }
+
+    @Override
+    void onDeviceConfigChanged() {
+        mHandler.post(() -> {
+            loadOverrideData();
+            recalculateActiveData();
+        });
+    }
+
+    @Override
+    void onDisplayChanged(PowerData data) {
+        mHandler.post(() -> {
+            setDisplayData(data);
+            recalculateActiveData();
+        });
+    }
+
+    @Override
+    void stop() {
+        if (mPmicMonitor != null) {
+            mPmicMonitor.shutdown();
+        }
+    }
+
+    /**
+     * Dumps the state of BrightnessPowerClamper.
+     */
+    public void dump(PrintWriter pw) {
+        pw.println("BrightnessPowerClamper:");
+        pw.println("  mCurrentAvgPowerConsumed=" + mCurrentAvgPowerConsumed);
+        pw.println("  mUniqueDisplayId=" + mUniqueDisplayId);
+        pw.println("  mCurrentThermalLevel=" + mCurrentThermalLevel);
+        pw.println("  mPowerThrottlingDataFromDDC=" + (mPowerThrottlingDataFromDDC == null ? "null"
+                : mPowerThrottlingDataFromDDC.toString()));
+        super.dump(pw);
+    }
+
+    private void recalculateActiveData() {
+        if (mUniqueDisplayId == null || mDataId == null) {
+            return;
+        }
+        mPowerThrottlingDataActive = mPowerThrottlingDataOverride
+                .getOrDefault(mUniqueDisplayId, Map.of()).getOrDefault(mDataId,
+                        mPowerThrottlingDataFromDDC);
+        if (mPowerThrottlingDataActive != null) {
+            if (mPmicMonitor != null) {
+                mPmicMonitor.stop();
+                mPmicMonitor.start();
+            }
+        } else {
+            if (mPmicMonitor != null) {
+                mPmicMonitor.stop();
+            }
+        }
+        recalculateBrightnessCap();
+    }
+
+    private void loadOverrideData() {
+        String throttlingDataOverride = mConfigParameterProvider.getPowerThrottlingData();
+        mPowerThrottlingDataOverride = DeviceConfigParsingUtils.parseDeviceConfigMap(
+                throttlingDataOverride, mDataPointMapper, mDataSetMapper);
+    }
+
+    private void setDisplayData(@NonNull PowerData data) {
+        mUniqueDisplayId = data.getUniqueDisplayId();
+        mDataId = data.getPowerThrottlingDataId();
+        mPowerThrottlingDataFromDDC = data.getPowerThrottlingData();
+        if (mPowerThrottlingDataFromDDC == null && !DEFAULT_ID.equals(mDataId)) {
+            Slog.wtf(TAG,
+                    "Power throttling data is missing for powerThrottlingDataId=" + mDataId);
+        }
+
+        mPowerThrottlingConfigData = data.getPowerThrottlingConfigData();
+        if (mPowerThrottlingConfigData == null) {
+            Slog.d(TAG,
+                    "Power throttling data is missing for configuration data.");
+        }
+    }
+
+    private void recalculateBrightnessCap() {
+        boolean isActive = false;
+        float targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+        float powerQuota = getPowerQuotaForThermalStatus(mCurrentThermalLevel);
+        if (mPowerThrottlingDataActive == null) {
+            return;
+        }
+        if (powerQuota > 0 && mCurrentAvgPowerConsumed > powerQuota) {
+            isActive = true;
+            // calculate new brightness Cap.
+            // Brightness has a linear relation to power-consumed.
+            targetBrightnessCap =
+                    (powerQuota / mCurrentAvgPowerConsumed) * PowerManager.BRIGHTNESS_MAX;
+            // Cap to lowest allowed brightness on device.
+            targetBrightnessCap = Math.max(targetBrightnessCap,
+                    mPowerThrottlingConfigData.brightnessLowestCapAllowed);
+        }
+
+        if (mBrightnessCap != targetBrightnessCap || mIsActive != isActive) {
+            mIsActive = isActive;
+            mBrightnessCap = targetBrightnessCap;
+            mChangeListener.onChanged();
+        }
+    }
+
+    private float getPowerQuotaForThermalStatus(@Temperature.ThrottlingStatus int thermalStatus) {
+        float powerQuota = 0f;
+        if (mPowerThrottlingDataActive != null) {
+            // Throttling levels are sorted by increasing severity
+            for (ThrottlingLevel level : mPowerThrottlingDataActive.throttlingLevels) {
+                if (level.thermalStatus <= thermalStatus) {
+                    powerQuota = level.powerQuotaMilliWatts;
+                } else {
+                    // Throttling levels that are greater than the current status are irrelevant
+                    break;
+                }
+            }
+        }
+        return powerQuota;
+    }
+
+    private void recalculatePowerQuotaChange(float avgPowerConsumed, int thermalStatus) {
+        mHandler.post(() -> {
+            mCurrentThermalLevel = thermalStatus;
+            mCurrentAvgPowerConsumed = avgPowerConsumed;
+            recalculateBrightnessCap();
+        });
+    }
+
+    private void start() {
+        if (mPowerThrottlingConfigData == null) {
+            return;
+        }
+        PowerChangeListener listener = (powerConsumed, thermalStatus) -> {
+            recalculatePowerQuotaChange(powerConsumed, thermalStatus);
+        };
+        mPmicMonitor =
+            mInjector.getPmicMonitor(listener, mPowerThrottlingConfigData.pollingWindowMillis);
+        mPmicMonitor.start();
+    }
+
+    public interface PowerData {
+        @NonNull
+        String getUniqueDisplayId();
+
+        @NonNull
+        String getPowerThrottlingDataId();
+
+        @Nullable
+        PowerThrottlingData getPowerThrottlingData();
+
+        @Nullable
+        PowerThrottlingConfigData getPowerThrottlingConfigData();
+    }
+
+    /**
+     * Power change listener
+     */
+    @FunctionalInterface
+    public interface PowerChangeListener {
+        /**
+         * Notifies that power state changed from power controller.
+         */
+        void onChanged(float avgPowerConsumed, @Temperature.ThrottlingStatus int thermalStatus);
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        PmicMonitor getPmicMonitor(PowerChangeListener listener, int pollingTime) {
+            return new PmicMonitor(listener, pollingTime);
+        }
+
+        DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+            return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index 652e6cf..39f0b13 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -105,16 +105,21 @@
     public void resetHdrConfig(HdrBrightnessData data, int width, int height,
             float minimumHdrPercentOfScreen, IBinder displayToken) {
         mHdrBrightnessData = data;
-        mHdrListener.mHdrMinPixels = (float) (width * height) * minimumHdrPercentOfScreen;
+        mHdrListener.mHdrMinPixels = minimumHdrPercentOfScreen <= 0 ? -1
+                : (float) (width * height) * minimumHdrPercentOfScreen;
         if (displayToken != mRegisteredDisplayToken) { // token changed, resubscribe
             if (mRegisteredDisplayToken != null) { // previous token not null, unsubscribe
                 mHdrListener.unregister(mRegisteredDisplayToken);
                 mHdrVisible = false;
+                mRegisteredDisplayToken = null;
             }
-            if (displayToken != null) { // new token not null, subscribe
+            // new token not null and hdr min % of the screen is set, subscribe.
+            // e.g. for virtual display, HBM data will be missing and HdrListener
+            // should not be registered
+            if (displayToken != null && mHdrListener.mHdrMinPixels > 0) {
                 mHdrListener.register(displayToken);
+                mRegisteredDisplayToken = displayToken;
             }
-            mRegisteredDisplayToken = displayToken;
         }
         recalculateBrightnessCap(data, mAmbientLux, mHdrVisible);
     }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
new file mode 100644
index 0000000..26784f23
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
@@ -0,0 +1,192 @@
+/*
+ * 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.brightness.clamper;
+
+import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.IThermalService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Temperature;
+import android.power.PowerStatsInternal;
+import android.util.IntArray;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Monitors the display consumed power and helps make informed decision,
+ * regarding overconsumption.
+ */
+public class PmicMonitor {
+    private static final String TAG = "PmicMonitor";
+
+    // The executor to periodically monitor the display power.
+    private final ScheduledExecutorService mExecutor;
+    @NonNull
+    private final PowerChangeListener mPowerChangeListener;
+    private final long mPowerMonitorPeriodConfigSecs;
+    private final PowerStatsInternal mPowerStatsInternal;
+    @VisibleForTesting final IThermalService mThermalService;
+    private ScheduledFuture<?> mPmicMonitorFuture;
+    private float mLastEnergyConsumed = 0;
+    private float mCurrentAvgPower = 0;
+    private Temperature mCurrentTemperature;
+    private long mCurrentTimestampMillis = 0;
+
+    PmicMonitor(PowerChangeListener listener, int powerMonitorPeriodConfigSecs) {
+        mPowerChangeListener = listener;
+        mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class);
+        mThermalService = IThermalService.Stub.asInterface(
+                ServiceManager.getService(Context.THERMAL_SERVICE));
+        // start a periodic worker thread.
+        mExecutor = Executors.newSingleThreadScheduledExecutor();
+        mPowerMonitorPeriodConfigSecs = (long) powerMonitorPeriodConfigSecs;
+    }
+
+    @Nullable
+    private Temperature getDisplayTemperature() {
+        Temperature retTemperature = null;
+        try {
+            Temperature[] temperatures;
+            // TODO b/279114539 Try DISPLAY first and then fallback to SKIN.
+            temperatures = mThermalService.getCurrentTemperaturesWithType(
+                        Temperature.TYPE_SKIN);
+            if (temperatures.length > 1) {
+                Slog.w(TAG, "Multiple skin temperatures not allowed!");
+            }
+            if (temperatures.length > 0) {
+                retTemperature = temperatures[0];
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "getDisplayTemperature failed" + e);
+        }
+        return retTemperature;
+    }
+
+    private void capturePeriodicDisplayPower() {
+        final EnergyConsumer[] energyConsumers = mPowerStatsInternal.getEnergyConsumerInfo();
+        if (energyConsumers == null || energyConsumers.length == 0) {
+            return;
+        }
+        final IntArray energyConsumerIds = new IntArray();
+        for (int i = 0; i < energyConsumers.length; i++) {
+            if (energyConsumers[i].type == EnergyConsumerType.DISPLAY) {
+                energyConsumerIds.add(energyConsumers[i].id);
+            }
+        }
+
+        if (energyConsumerIds.size() == 0) {
+            Slog.w(TAG, "DISPLAY energyConsumerIds size is null");
+            return;
+        }
+        CompletableFuture<EnergyConsumerResult[]> futureECRs =
+                mPowerStatsInternal.getEnergyConsumedAsync(energyConsumerIds.toArray());
+        if (futureECRs == null) {
+            Slog.w(TAG, "Energy consumers results are null");
+            return;
+        }
+
+        EnergyConsumerResult[] displayResults;
+        try {
+            displayResults = futureECRs.get();
+        } catch (InterruptedException e) {
+            Slog.w(TAG, "timeout or interrupt reading getEnergyConsumedAsync failed", e);
+            displayResults = null;
+        } catch (ExecutionException e) {
+            Slog.wtf(TAG, "exception reading getEnergyConsumedAsync: ", e);
+            displayResults = null;
+        }
+
+        if (displayResults == null || displayResults.length == 0) {
+            Slog.w(TAG, "displayResults are null");
+            return;
+        }
+        // Support for only 1 display rail.
+        float energyConsumed = (displayResults[0].energyUWs - mLastEnergyConsumed);
+        float timeIntervalSeconds =
+                (displayResults[0].timestampMs - mCurrentTimestampMillis) / 1000.f;
+        // energy consumed is received in microwatts-seconds.
+        float currentPower = energyConsumed / timeIntervalSeconds;
+        // convert power received in microwatts to milliwatts.
+        currentPower = currentPower / 1000.f;
+
+        // capture thermal state.
+        Temperature displayTemperature = getDisplayTemperature();
+        mCurrentAvgPower = currentPower;
+        mCurrentTemperature = displayTemperature;
+        mLastEnergyConsumed = displayResults[0].energyUWs;
+        mCurrentTimestampMillis = displayResults[0].timestampMs;
+        if (mCurrentTemperature != null) {
+            mPowerChangeListener.onChanged(mCurrentAvgPower, mCurrentTemperature.getStatus());
+        }
+    }
+
+    /**
+    * Start polling the power IC.
+    */
+    public void start() {
+        if (mPowerStatsInternal == null) {
+            Slog.w(TAG, "Power stats service not found for monitoring.");
+            return;
+        }
+        if (mThermalService == null) {
+            Slog.w(TAG, "Thermal service not found.");
+            return;
+        }
+        if (mPmicMonitorFuture == null) {
+            mPmicMonitorFuture = mExecutor.scheduleAtFixedRate(
+                                    this::capturePeriodicDisplayPower,
+                                    mPowerMonitorPeriodConfigSecs,
+                                    mPowerMonitorPeriodConfigSecs,
+                                    TimeUnit.SECONDS);
+        } else {
+            Slog.e(TAG, "already scheduled, stop() called before start.");
+        }
+    }
+
+    /**
+     * Stop polling to power IC.
+     */
+    public void stop() {
+        if (mPmicMonitorFuture != null) {
+            mPmicMonitorFuture.cancel(true);
+            mPmicMonitorFuture = null;
+        }
+    }
+
+    /**
+     * Shutdown power IC service and worker thread.
+     */
+    public void shutdown() {
+        mExecutor.shutdownNow();
+    }
+}
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 d8831fa..e3aa161 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -78,6 +78,7 @@
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.twilight.TwilightListener;
 import com.android.server.twilight.TwilightManager;
 import com.android.server.twilight.TwilightState;
@@ -148,10 +149,12 @@
             1f, 1f, 1f, 1f
     };
 
+    private final DisplayManagerFlags mDisplayManagerFlags = new DisplayManagerFlags();
+
     @VisibleForTesting
     final DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController =
             new DisplayWhiteBalanceTintController(
-                    LocalServices.getService(DisplayManagerInternal.class));
+                    LocalServices.getService(DisplayManagerInternal.class), mDisplayManagerFlags);
     private final NightDisplayTintController mNightDisplayTintController =
             new NightDisplayTintController();
     private final TintController mGlobalSaturationTintController =
@@ -231,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) {
@@ -470,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;
@@ -716,15 +730,16 @@
                         tintController.computeMatrixForCct(to));
                 tintController.setAppliedCct(to);
             } else {
+                final long duration = tintController.getTransitionDurationMilliseconds(to > from);
                 Slog.d(TAG, tintController.getClass().getSimpleName() + " animation started: toCct="
-                        + to + " fromCct=" + from);
+                        + to + " fromCct=" + from + " with duration=" + duration);
                 ValueAnimator valueAnimator = ValueAnimator.ofInt(from, to);
                 tintController.setAnimator(valueAnimator);
                 final CctEvaluator evaluator = tintController.getEvaluator();
                 if (evaluator != null) {
                     valueAnimator.setEvaluator(evaluator);
                 }
-                valueAnimator.setDuration(tintController.getTransitionDurationMilliseconds());
+                valueAnimator.setDuration(duration);
                 valueAnimator.setInterpolator(AnimationUtils.loadInterpolator(
                         getContext(), android.R.interpolator.linear));
                 valueAnimator.addUpdateListener((ValueAnimator animator) -> {
diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
index bf0139f..b9fd1d0 100644
--- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
+++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
@@ -34,6 +34,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 import java.io.PrintWriter;
 
@@ -65,6 +66,8 @@
     boolean mSetUp = false;
     private final float[] mMatrixDisplayWhiteBalance = new float[16];
     private long mTransitionDuration;
+    private long mTransitionDurationIncrease;
+    private long mTransitionDurationDecrease;
     private Boolean mIsAvailable;
     // This feature becomes disallowed if the device is in an unsupported strong/light state.
     private boolean mIsAllowed = true;
@@ -74,8 +77,12 @@
 
     private final DisplayManagerInternal mDisplayManagerInternal;
 
-    DisplayWhiteBalanceTintController(DisplayManagerInternal dm) {
+    private final DisplayManagerFlags mDisplayManagerFlags;
+
+    DisplayWhiteBalanceTintController(DisplayManagerInternal dm,
+            DisplayManagerFlags displayManagerFlags) {
         mDisplayManagerInternal = dm;
+        mDisplayManagerFlags = displayManagerFlags;
     }
 
     @Override
@@ -139,6 +146,16 @@
         mTransitionDuration = res.getInteger(
                 R.integer.config_displayWhiteBalanceTransitionTime);
 
+        if (!mDisplayManagerFlags.isAdaptiveTone2Enabled()) {
+            mTransitionDurationDecrease = mTransitionDuration;
+            mTransitionDurationIncrease = mTransitionDuration;
+        } else {
+            mTransitionDurationIncrease = res.getInteger(
+                    R.integer.config_displayWhiteBalanceTransitionTimeIncrease);
+            mTransitionDurationDecrease = res.getInteger(
+                    R.integer.config_displayWhiteBalanceTransitionTimeDecrease);
+        }
+
         int[] cctRangeMinimums = res.getIntArray(
                 R.array.config_displayWhiteBalanceDisplayRangeMinimums);
         int[] steps = res.getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
@@ -323,6 +340,11 @@
     }
 
     @Override
+    public long getTransitionDurationMilliseconds(boolean isIncreasing) {
+        return isIncreasing ? mTransitionDurationIncrease : mTransitionDurationDecrease;
+    }
+
+    @Override
     public void dump(PrintWriter pw) {
         synchronized (mLock) {
             pw.println("    mSetUp = " + mSetUp);
@@ -348,6 +370,9 @@
             pw.println("    mMatrixDisplayWhiteBalance = "
                     + matrixToString(mMatrixDisplayWhiteBalance, 4));
             pw.println("    mIsAllowed = " + mIsAllowed);
+            pw.println("    mTransitionDuration = " + mTransitionDuration);
+            pw.println("    mTransitionDurationIncrease = " + mTransitionDurationIncrease);
+            pw.println("    mTransitionDurationDecrease = " + mTransitionDurationDecrease);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java
index 384333a..716661d 100644
--- a/services/core/java/com/android/server/display/color/TintController.java
+++ b/services/core/java/com/android/server/display/color/TintController.java
@@ -75,6 +75,10 @@
         return TRANSITION_DURATION;
     }
 
+    public long getTransitionDurationMilliseconds(boolean direction) {
+        return TRANSITION_DURATION;
+    }
+
     /**
      * Dump debug information.
      */
diff --git a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
index 23ffe59..465584c 100644
--- a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
+++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
@@ -77,6 +77,12 @@
     // Test parameters
     // usage e.g.: adb shell device_config put display_manager refresh_rate_in_hbm_sunlight 90
 
+    // allows to customize power throttling data
+    public String getPowerThrottlingData() {
+        return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_POWER_THROTTLING_DATA, null);
+    }
+
     // allows to customize brightness throttling data
     public String getBrightnessThrottlingData() {
         return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
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 d5382cb..fae8383 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -47,6 +47,10 @@
             Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1,
             Flags::enableAdaptiveToneImprovements1);
 
+    private final FlagState mAdaptiveToneImprovements2 = new FlagState(
+            Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_2,
+            Flags::enableAdaptiveToneImprovements2);
+
     private final FlagState mDisplayOffloadFlagState = new FlagState(
             Flags.FLAG_ENABLE_DISPLAY_OFFLOAD,
             Flags::enableDisplayOffload);
@@ -55,20 +59,39 @@
             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);
+
+    private final FlagState mPowerThrottlingClamperFlagState = new FlagState(
+            Flags.FLAG_ENABLE_POWER_THROTTLING_CLAMPER,
+            Flags::enablePowerThrottlingClamper);
+
     /** Returns whether connected display management is enabled or not. */
     public boolean isConnectedDisplayManagementEnabled() {
         return mConnectedDisplayManagementFlagState.isEnabled();
     }
 
-    /** Returns whether hdr clamper is enabled on not*/
+    /** Returns whether NBM Controller is enabled or not. */
     public boolean isNbmControllerEnabled() {
         return mNbmControllerFlagState.isEnabled();
     }
 
+    /** Returns whether hdr clamper is enabled on not. */
     public boolean isHdrClamperEnabled() {
         return mHdrClamperFlagState.isEnabled();
     }
 
+    /** Returns whether power throttling clamper is enabled on not. */
+    public boolean isPowerThrottlingClamperEnabled() {
+        return mPowerThrottlingClamperFlagState.isEnabled();
+    }
+
+
     /**
      * Returns whether adaptive tone improvements are enabled
      */
@@ -76,6 +99,13 @@
         return mAdaptiveToneImprovements1.isEnabled();
     }
 
+    /**
+     * Returns whether adaptive tone improvements are enabled
+     */
+    public boolean isAdaptiveTone2Enabled() {
+        return mAdaptiveToneImprovements2.isEnabled();
+    }
+
     /** Returns whether resolution range voting feature is enabled or not. */
     public boolean isDisplayResolutionRangeVotingEnabled() {
         return isExternalDisplayLimitModeEnabled();
@@ -108,6 +138,15 @@
         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();
+    }
+
     private static class FlagState {
 
         private final String mName;
@@ -121,7 +160,6 @@
             mFlagFunction = flagFunction;
         }
 
-        // TODO(b/297159910): Simplify using READ-ONLY flags when available.
         private boolean isEnabled() {
             if (mEnabledSet) {
                 if (DEBUG) {
@@ -138,19 +176,13 @@
         }
 
         private boolean flagOrSystemProperty(Supplier<Boolean> flagFunction, String flagName) {
+            boolean flagValue = flagFunction.get();
             // TODO(b/299462337) Remove when the infrastructure is ready.
-            if ((Build.IS_ENG || Build.IS_USERDEBUG)
-                    && SystemProperties.getBoolean("persist.sys." + flagName, false)) {
-                return true;
+            if (Build.IS_ENG || Build.IS_USERDEBUG) {
+                return SystemProperties.getBoolean("persist.sys." + flagName + "-override",
+                        flagValue);
             }
-            try {
-                return flagFunction.get();
-            } catch (Throwable ex) {
-                if (DEBUG) {
-                    Slog.i(TAG, "Flags not ready yet. Return false for " + flagName, ex);
-                }
-                return false;
-            }
+            return flagValue;
         }
     }
 }
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 542f26c..9ab9c9d 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
@@ -27,6 +27,14 @@
 }
 
 flag {
+    name: "enable_power_throttling_clamper"
+    namespace: "display_manager"
+    description: "Feature flag for Power Throttling Clamper"
+    bug: "294777007"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "enable_adaptive_tone_improvements_1"
     namespace: "display_manager"
     description: "Feature flag for Adaptive Tone Improvements"
@@ -35,6 +43,14 @@
 }
 
 flag {
+    name: "enable_adaptive_tone_improvements_2"
+    namespace: "display_manager"
+    description: "Feature flag for Further Adaptive Tone Improvements"
+    bug: "294762632"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "enable_display_resolution_range_voting"
     namespace: "display_manager"
     description: "Feature flag to enable voting for ranges of resolutions"
@@ -72,3 +88,19 @@
     bug: "299521647"
     is_fixed_read_only: true
 }
+
+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"
+    bug: "211737588"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index d9ec3de..40cb3303 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -80,7 +80,8 @@
         createDisplayLocked(address, /* isDefault= */ true, /* isEnabled= */ true,
                 DEFAULT_DISPLAY_GROUP_NAME, idProducer, POSITION_UNKNOWN,
                 /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null,
-                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null,
+                /* powerThrottlingMapId= */ null);
     }
 
     /**
@@ -97,6 +98,7 @@
      * @param refreshRateZoneId Layout limited refresh rate zone name.
      * @param refreshRateThermalThrottlingMapId Name of which refresh rate throttling
      *                                          policy should be used.
+     * @param powerThrottlingMapId Name of which power throttling policy should be used.
      *
      * @exception IllegalArgumentException When a default display owns a display group other than
      *            DEFAULT_DISPLAY_GROUP.
@@ -106,7 +108,8 @@
             String displayGroupName, DisplayIdProducer idProducer, int position,
             @Nullable DisplayAddress leadDisplayAddress, String brightnessThrottlingMapId,
             @Nullable String refreshRateZoneId,
-            @Nullable String refreshRateThermalThrottlingMapId) {
+            @Nullable String refreshRateThermalThrottlingMapId,
+            @Nullable String powerThrottlingMapId) {
         if (contains(address)) {
             Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
             return;
@@ -139,7 +142,7 @@
 
         final Display display = new Display(address, logicalDisplayId, isEnabled, displayGroupName,
                 brightnessThrottlingMapId, position, leadDisplayAddress, refreshRateZoneId,
-                refreshRateThermalThrottlingMapId);
+                refreshRateThermalThrottlingMapId, powerThrottlingMapId);
 
         mDisplays.add(display);
     }
@@ -311,6 +314,9 @@
         @Nullable
         private final String mThermalRefreshRateThrottlingMapId;
 
+        @Nullable
+        private final String mPowerThrottlingMapId;
+
         // The ID of the lead display that this display will follow in a layout. -1 means no lead.
         // This is determined using {@code mLeadDisplayAddress}.
         private int mLeadDisplayId;
@@ -318,7 +324,8 @@
         private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,
                 @NonNull String displayGroupName, String brightnessThrottlingMapId, int position,
                 @Nullable DisplayAddress leadDisplayAddress, @Nullable String refreshRateZoneId,
-                @Nullable String refreshRateThermalThrottlingMapId) {
+                @Nullable String refreshRateThermalThrottlingMapId,
+                @Nullable String powerThrottlingMapId) {
             mAddress = address;
             mLogicalDisplayId = logicalDisplayId;
             mIsEnabled = isEnabled;
@@ -328,6 +335,7 @@
             mLeadDisplayAddress = leadDisplayAddress;
             mRefreshRateZoneId = refreshRateZoneId;
             mThermalRefreshRateThrottlingMapId = refreshRateThermalThrottlingMapId;
+            mPowerThrottlingMapId = powerThrottlingMapId;
             mLeadDisplayId = NO_LEAD_DISPLAY;
         }
 
@@ -344,6 +352,7 @@
                     + ", mLeadDisplayId: " + mLeadDisplayId
                     + ", mLeadDisplayAddress: " + mLeadDisplayAddress
                     + ", mThermalRefreshRateThrottlingMapId: " + mThermalRefreshRateThrottlingMapId
+                    + ", mPowerThrottlingMapId: " + mPowerThrottlingMapId
                     + "}";
         }
 
@@ -366,7 +375,9 @@
                     && this.mLeadDisplayId == otherDisplay.mLeadDisplayId
                     && Objects.equals(mLeadDisplayAddress, otherDisplay.mLeadDisplayAddress)
                     && Objects.equals(mThermalRefreshRateThrottlingMapId,
-                    otherDisplay.mThermalRefreshRateThrottlingMapId);
+                    otherDisplay.mThermalRefreshRateThrottlingMapId)
+                    && Objects.equals(mPowerThrottlingMapId,
+                    otherDisplay.mPowerThrottlingMapId);
         }
 
         @Override
@@ -382,6 +393,7 @@
             result = 31 * result + mLeadDisplayId;
             result = 31 * result + Objects.hashCode(mLeadDisplayAddress);
             result = 31 * result + Objects.hashCode(mThermalRefreshRateThrottlingMapId);
+            result = 31 * result + Objects.hashCode(mPowerThrottlingMapId);
             return result;
         }
 
@@ -441,6 +453,15 @@
             return mThermalRefreshRateThrottlingMapId;
         }
 
+        /**
+         * Gets the id of the power throttling map that should be used.
+         * @return The ID of the power throttling map that this display should use,
+         *         null if unspecified, will fall back to default.
+         */
+        public String getPowerThrottlingMapId() {
+            return mPowerThrottlingMapId;
+        }
+
         private void setLeadDisplayId(int id) {
             mLeadDisplayId = id;
         }
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 71ea8cc..ca23844 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -22,6 +22,7 @@
 import static android.view.Display.Mode.INVALID_MODE_ID;
 
 import static com.android.server.display.DisplayDeviceConfig.DEFAULT_LOW_REFRESH_RATE;
+import static com.android.internal.display.RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay;
 
 import android.annotation.IntegerRes;
 import android.annotation.NonNull;
@@ -176,6 +177,8 @@
 
     private final boolean mIsDisplaysRefreshRatesSynchronizationEnabled;
 
+    private final boolean mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled;
+
     public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler,
             @NonNull DisplayManagerFlags displayManagerFlags) {
         this(context, handler, new RealInjector(context), displayManagerFlags);
@@ -191,6 +194,8 @@
             .isExternalDisplayLimitModeEnabled();
         mIsDisplaysRefreshRatesSynchronizationEnabled = displayManagerFlags
             .isDisplaysRefreshRatesSynchronizationEnabled();
+        mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled = displayManagerFlags
+                .isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled();
         mContext = context;
         mHandler = new DisplayModeDirectorHandler(handler.getLooper());
         mInjector = injector;
@@ -1193,8 +1198,7 @@
         public void observe() {
             final ContentResolver cr = mContext.getContentResolver();
             mInjector.registerPeakRefreshRateObserver(cr, this);
-            cr.registerContentObserver(mMinRefreshRateSetting, false /*notifyDescendants*/, this,
-                    UserHandle.USER_SYSTEM);
+            mInjector.registerMinRefreshRateObserver(cr, this);
             cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this,
                     UserHandle.USER_SYSTEM);
             cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/,
@@ -1292,10 +1296,34 @@
 
         private void updateRefreshRateSettingLocked() {
             final ContentResolver cr = mContext.getContentResolver();
+            float highestRefreshRate = findHighestRefreshRateForDefaultDisplay(mContext);
+
             float minRefreshRate = Settings.System.getFloatForUser(cr,
                     Settings.System.MIN_REFRESH_RATE, 0f, cr.getUserId());
+            if (Float.isInfinite(minRefreshRate)) {
+                // Infinity means that we want the highest possible refresh rate
+                minRefreshRate = highestRefreshRate;
+
+                if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) {
+                    // The flag had been turned off, we need to restore the original value
+                    Settings.System.putFloatForUser(cr,
+                            Settings.System.MIN_REFRESH_RATE, minRefreshRate, cr.getUserId());
+                }
+            }
+
             float peakRefreshRate = Settings.System.getFloatForUser(cr,
                     Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate, cr.getUserId());
+            if (Float.isInfinite(peakRefreshRate)) {
+                // Infinity means that we want the highest possible refresh rate
+                peakRefreshRate = highestRefreshRate;
+
+                if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) {
+                    // The flag had been turned off, we need to restore the original value
+                    Settings.System.putFloatForUser(cr,
+                            Settings.System.PEAK_REFRESH_RATE, peakRefreshRate, cr.getUserId());
+                }
+            }
+
             updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate);
         }
 
@@ -3082,6 +3110,7 @@
 
     interface Injector {
         Uri PEAK_REFRESH_RATE_URI = Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
+        Uri MIN_REFRESH_RATE_URI = Settings.System.getUriFor(Settings.System.MIN_REFRESH_RATE);
 
         @NonNull
         DeviceConfigInterface getDeviceConfig();
@@ -3089,6 +3118,9 @@
         void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
                 @NonNull ContentObserver observer);
 
+        void registerMinRefreshRateObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer);
+
         void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener,
                 Handler handler);
 
@@ -3140,6 +3172,13 @@
         }
 
         @Override
+        public void registerMinRefreshRateObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            cr.registerContentObserver(MIN_REFRESH_RATE_URI, false /*notifyDescendants*/,
+                    observer, UserHandle.USER_SYSTEM);
+        }
+
+        @Override
         public void registerDisplayListener(DisplayManager.DisplayListener listener,
                 Handler handler) {
             getDisplayManager().registerDisplayListener(listener, handler);
diff --git a/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java b/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java
new file mode 100644
index 0000000..f683e81
--- /dev/null
+++ b/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.notifications;
+
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED;
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE;
+import static android.hardware.usb.DisplayPortAltModeInfo.LINK_TRAINING_STATUS_FAILURE;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.hardware.usb.DisplayPortAltModeInfo;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbManager.DisplayPortAltModeInfoListener;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.feature.DisplayManagerFlags;
+
+/**
+ * Detects usb issues related to an external display connected.
+ */
+public class ConnectedDisplayUsbErrorsDetector implements DisplayPortAltModeInfoListener {
+    private static final String TAG = "ConnectedDisplayUsbErrorsDetector";
+
+    /**
+     * Dependency injection for {@link ConnectedDisplayUsbErrorsDetector}.
+     */
+    public interface Injector {
+
+        /**
+         * @return {@link UsbManager} service.
+         */
+        UsbManager getUsbManager();
+    }
+
+    /**
+     * USB errors listener
+     */
+    public interface Listener {
+
+        /**
+         * Link training failure callback.
+         */
+        void onDisplayPortLinkTrainingFailure();
+
+        /**
+         * DisplayPort capable device plugged-in, but cable is not supporting DisplayPort.
+         */
+        void onCableNotCapableDisplayPort();
+    }
+
+    private Listener mListener;
+    private final Injector mInjector;
+    private final Context mContext;
+    private final boolean mIsConnectedDisplayErrorHandlingEnabled;
+
+    ConnectedDisplayUsbErrorsDetector(@NonNull final DisplayManagerFlags flags,
+            @NonNull final Context context) {
+        this(flags, context, () -> context.getSystemService(UsbManager.class));
+    }
+
+    @VisibleForTesting
+    ConnectedDisplayUsbErrorsDetector(@NonNull final DisplayManagerFlags flags,
+            @NonNull final Context context, @NonNull final Injector injector) {
+        mContext = context;
+        mInjector = injector;
+        mIsConnectedDisplayErrorHandlingEnabled =
+                flags.isConnectedDisplayErrorHandlingEnabled();
+    }
+
+    /** Register listener for usb error events. */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    void registerListener(final Listener listener) {
+        if (!mIsConnectedDisplayErrorHandlingEnabled) {
+            return;
+        }
+
+        final var usbManager = mInjector.getUsbManager();
+        if (usbManager == null) {
+            Slog.e(TAG, "UsbManager is null");
+            return;
+        }
+
+        mListener = listener;
+
+        try {
+            usbManager.registerDisplayPortAltModeInfoListener(mContext.getMainExecutor(), this);
+        } catch (IllegalStateException e) {
+            Slog.e(TAG, "Failed to register listener", e);
+        }
+    }
+
+    /**
+     * Callback upon changes in {@link DisplayPortAltModeInfo}.
+     * @param portId    String describing the {@link android.hardware.usb.UsbPort} that was changed.
+     * @param info      New {@link DisplayPortAltModeInfo} for the corresponding portId.
+     */
+    @Override
+    public void onDisplayPortAltModeInfoChanged(@NonNull String portId,
+            @NonNull DisplayPortAltModeInfo info) {
+        if (mListener == null) {
+            return;
+        }
+
+        if (DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED == info.getPartnerSinkStatus()
+                && DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE == info.getCableStatus()
+        ) {
+            mListener.onCableNotCapableDisplayPort();
+            return;
+        }
+
+        if (LINK_TRAINING_STATUS_FAILURE == info.getLinkTrainingStatus()) {
+            mListener.onDisplayPortLinkTrainingFailure();
+            return;
+        }
+    }
+}
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..5cdef38
--- /dev/null
+++ b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.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 implements ConnectedDisplayUsbErrorsDetector.Listener {
+    /** Dependency injection interface for {@link DisplayNotificationManager} */
+    public interface Injector {
+        /** Get {@link NotificationManager} service or null if not available. */
+        @Nullable
+        NotificationManager getNotificationManager();
+
+        /** Get {@link ConnectedDisplayUsbErrorsDetector} or null if not available. */
+        @Nullable
+        ConnectedDisplayUsbErrorsDetector getUsbErrorsDetector();
+    }
+
+    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;
+    private ConnectedDisplayUsbErrorsDetector mConnectedDisplayUsbErrorsDetector;
+
+    public DisplayNotificationManager(final DisplayManagerFlags flags, final Context context) {
+        this(flags, context, new Injector() {
+            @Nullable
+            @Override
+            public NotificationManager getNotificationManager() {
+                return context.getSystemService(NotificationManager.class);
+            }
+
+            @Nullable
+            @Override
+            public ConnectedDisplayUsbErrorsDetector getUsbErrorsDetector() {
+                return new ConnectedDisplayUsbErrorsDetector(flags, context);
+            }
+        });
+    }
+
+    @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;
+        }
+
+        mConnectedDisplayUsbErrorsDetector = mInjector.getUsbErrorsDetector();
+        if (mConnectedDisplayUsbErrorsDetector != null) {
+            mConnectedDisplayUsbErrorsDetector.registerListener(this);
+        }
+    }
+
+    /**
+     * Display error notification upon DisplayPort link training failure.
+     */
+    @Override
+    public void onDisplayPortLinkTrainingFailure() {
+        if (!mConnectedDisplayErrorHandlingEnabled) {
+            Slog.d(TAG, "onDisplayPortLinkTrainingFailure:"
+                                + " mConnectedDisplayErrorHandlingEnabled is false");
+            return;
+        }
+
+        sendErrorNotification(createErrorNotification(
+                R.string.connected_display_unavailable_notification_title,
+                R.string.connected_display_unavailable_notification_content));
+    }
+
+    /**
+     * Display error notification upon cable not capable of DisplayPort connected to a device
+     * capable of DisplayPort.
+     */
+    @Override
+    public void onCableNotCapableDisplayPort() {
+        if (!mConnectedDisplayErrorHandlingEnabled) {
+            Slog.d(TAG, "onCableNotCapableDisplayPort:"
+                                + " mConnectedDisplayErrorHandlingEnabled is false");
+            return;
+        }
+
+        sendErrorNotification(createErrorNotification(
+                R.string.connected_display_cable_dont_support_displays_notification_title,
+                R.string.connected_display_cable_dont_support_displays_notification_content));
+    }
+
+    /**
+     * 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/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index c6a50ed..7b844a0 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -48,6 +48,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
+import com.android.text.flags.Flags;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -240,21 +241,35 @@
         mContext = context;
         mIsSafeMode = safeMode;
 
-        SystemServerInitThreadPool.submit(() -> {
-            initialize();
+        if (Flags.useOptimizedBoottimeFontLoading()) {
+            Slog.i(TAG, "Using optimized boot-time font loading.");
+            SystemServerInitThreadPool.submit(() -> {
+                initialize();
 
-            // Set system font map only if there is updatable font directory.
-            // If there is no updatable font directory, `initialize` will have already loaded the
-            // system font map, so there's no need to set the system font map again here.
-            if  (mUpdatableFontDir != null) {
-                try {
-                    Typeface.setSystemFontMap(getCurrentFontMap());
-                } catch (IOException | ErrnoException e) {
-                    Slog.w(TAG, "Failed to set system font map of system_server");
+                // Set system font map only if there is updatable font directory.
+                // If there is no updatable font directory, `initialize` will have already loaded
+                // the system font map, so there's no need to set the system font map again here.
+                synchronized (mUpdatableFontDirLock) {
+                    if  (mUpdatableFontDir != null) {
+                        setSystemFontMap();
+                    }
                 }
-            }
+                serviceStarted.complete(null);
+            }, "FontManagerService_create");
+        } else {
+            Slog.i(TAG, "Not using optimized boot-time font loading.");
+            initialize();
+            setSystemFontMap();
             serviceStarted.complete(null);
-        }, "FontManagerService_create");
+        }
+    }
+
+    private void setSystemFontMap() {
+        try {
+            Typeface.setSystemFontMap(getCurrentFontMap());
+        } catch (IOException | ErrnoException e) {
+            Slog.w(TAG, "Failed to set system font map of system_server");
+        }
     }
 
     @Nullable
@@ -291,9 +306,11 @@
         synchronized (mUpdatableFontDirLock) {
             mUpdatableFontDir = createUpdatableFontDir();
             if (mUpdatableFontDir == null) {
-                // If fs-verity is not supported, load preinstalled system font map and use it for
-                // all apps.
-                Typeface.loadPreinstalledSystemFontMap();
+                if (Flags.useOptimizedBoottimeFontLoading()) {
+                    // If fs-verity is not supported, load preinstalled system font map and use it
+                    // for all apps.
+                    Typeface.loadPreinstalledSystemFontMap();
+                }
                 setSerializedFontMap(serializeSystemServerFontMap());
                 return;
             }
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index cd3d2f0..0f40ca0 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -619,8 +619,8 @@
         }
 
         return new FontConfig(
-                config.getFontFamilies(), config.getAliases(), mergedFamilies, mLastModifiedMillis,
-                mConfigVersion);
+                config.getFontFamilies(), config.getAliases(), mergedFamilies,
+                config.getLocaleFallbackCustomizations(), mLastModifiedMillis, mConfigVersion);
     }
 
     private PersistentSystemFontConfig.Config readPersistentConfig() {
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 429db5e..c28a68b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4951,6 +4951,11 @@
         AudioDeviceAttributes attributes = new AudioDeviceAttributes(
                 AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI_EARC, "", "",
                 new ArrayList<AudioProfile>(), audioDescriptors);
+        // Set SAM to ON whenever CEC is disabled. Failure to do so may result in the absence
+        // of sound when CEC is disabled and eARC is enabled due to SAM being in the off state.
+        if (!isCecControlEnabled()) {
+            setSystemAudioActivated(true);
+        }
         getAudioManager().setWiredDeviceConnectionState(attributes, enabled ? 1 : 0);
     }
 
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 0eb620f..8580b96 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -71,6 +71,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.InputMethodSubtypeHandle;
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.notification.SystemNotificationChannels;
@@ -99,7 +100,7 @@
  *
  * @hide
  */
-final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
+class KeyboardLayoutManager implements InputManager.InputDeviceListener {
 
     private static final String TAG = "KeyboardLayoutManager";
 
@@ -1245,11 +1246,17 @@
                         isFirstConfiguration);
         for (int i = 0; i < imeInfoList.size(); i++) {
             KeyboardLayoutInfo layoutInfo = layoutInfoList.get(i);
-            boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null;
-            configurationEventBuilder.addLayoutSelection(imeInfoList.get(i).mImeSubtype,
-                    noLayoutFound ? null : getKeyboardLayout(layoutInfo.mDescriptor),
-                    noLayoutFound ? LAYOUT_SELECTION_CRITERIA_DEFAULT
-                            : layoutInfo.mSelectionCriteria);
+            String layoutName = null;
+            int layoutSelectionCriteria = LAYOUT_SELECTION_CRITERIA_DEFAULT;
+            if (layoutInfo != null && layoutInfo.mDescriptor != null) {
+                layoutSelectionCriteria = layoutInfo.mSelectionCriteria;
+                KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(layoutInfo.mDescriptor);
+                if (d != null) {
+                    layoutName = d.keyboardLayoutName;
+                }
+            }
+            configurationEventBuilder.addLayoutSelection(imeInfoList.get(i).mImeSubtype, layoutName,
+                    layoutSelectionCriteria);
         }
         KeyboardMetricsCollector.logKeyboardConfiguredAtom(configurationEventBuilder.build());
     }
@@ -1295,7 +1302,8 @@
     }
 
     @SuppressLint("MissingPermission")
-    private List<ImeInfo> getImeInfoListForLayoutMapping() {
+    @VisibleForTesting
+    public List<ImeInfo> getImeInfoListForLayoutMapping() {
         List<ImeInfo> imeInfoList = new ArrayList<>();
         UserManager userManager = Objects.requireNonNull(
                 mContext.getSystemService(UserManager.class));
@@ -1402,7 +1410,8 @@
         }
     }
 
-    private static class ImeInfo {
+    @VisibleForTesting
+    public static class ImeInfo {
         @UserIdInt int mUserId;
         @NonNull InputMethodSubtypeHandle mImeSubtypeHandle;
         @Nullable InputMethodSubtype mImeSubtype;
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index 08e5977..2dd2a16 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -491,7 +491,7 @@
             private final InputDevice mInputDevice;
             private boolean mIsFirstConfiguration;
             private final List<InputMethodSubtype> mImeSubtypeList = new ArrayList<>();
-            private final List<KeyboardLayout> mSelectedLayoutList = new ArrayList<>();
+            private final List<String> mSelectedLayoutList = new ArrayList<>();
             private final List<Integer> mLayoutSelectionCriteriaList = new ArrayList<>();
 
             public Builder(@NonNull InputDevice inputDevice) {
@@ -511,7 +511,7 @@
              * Adds keyboard layout configuration info for a particular IME subtype language
              */
             public Builder addLayoutSelection(@NonNull InputMethodSubtype imeSubtype,
-                    @Nullable KeyboardLayout selectedLayout,
+                    @Nullable String selectedLayout,
                     @LayoutSelectionCriteria int layoutSelectionCriteria) {
                 Objects.requireNonNull(imeSubtype, "IME subtype provided should not be null");
                 if (!isValidSelectionCriteria(layoutSelectionCriteria)) {
@@ -533,7 +533,6 @@
                 }
                 List<LayoutConfiguration> configurationList = new ArrayList<>();
                 for (int i = 0; i < size; i++) {
-                    KeyboardLayout selectedLayout = mSelectedLayoutList.get(i);
                     @LayoutSelectionCriteria int layoutSelectionCriteria =
                             mLayoutSelectionCriteriaList.get(i);
                     InputMethodSubtype imeSubtype = mImeSubtypeList.get(i);
@@ -552,9 +551,9 @@
                             imeSubtype.getPhysicalKeyboardHintLayoutType());
 
                     // Sanitize null values
-                    String keyboardLayoutName =
-                            selectedLayout == null ? DEFAULT_LAYOUT_NAME
-                                    : selectedLayout.getLabel();
+                    String keyboardLayoutName = mSelectedLayoutList.get(i) == null
+                            ? DEFAULT_LAYOUT_NAME
+                            : mSelectedLayoutList.get(i);
 
                     configurationList.add(
                             new LayoutConfiguration(keyboardLayoutType, keyboardLanguageTag,
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/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index d4578dc..4851a81 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -486,9 +486,12 @@
                 Settings.Secure.DEFAULT_INPUT_METHOD,
                 userId);
         if (!TextUtils.isEmpty(currentInputMethod)) {
-            String inputMethodPkgName = ComponentName
-                    .unflattenFromString(currentInputMethod)
-                    .getPackageName();
+            ComponentName componentName = ComponentName.unflattenFromString(currentInputMethod);
+            if (componentName == null) {
+                Slog.d(TAG, "inValid input method");
+                return false;
+            }
+            String inputMethodPkgName = componentName.getPackageName();
             int inputMethodUid = getPackageUid(inputMethodPkgName, userId);
             return inputMethodUid >= 0 && UserHandle.isSameApp(Binder.getCallingUid(),
                     inputMethodUid);
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/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java
index b9c9bae..5d5d59b 100644
--- a/services/core/java/com/android/server/media/AudioAttributesUtils.java
+++ b/services/core/java/com/android/server/media/AudioAttributesUtils.java
@@ -48,6 +48,8 @@
             case AudioDeviceInfo.TYPE_DOCK_ANALOG:
                 return MediaRoute2Info.TYPE_DOCK;
             case AudioDeviceInfo.TYPE_HDMI:
+            case AudioDeviceInfo.TYPE_HDMI_ARC:
+            case AudioDeviceInfo.TYPE_HDMI_EARC:
                 return MediaRoute2Info.TYPE_HDMI;
             case AudioDeviceInfo.TYPE_USB_DEVICE:
                 return MediaRoute2Info.TYPE_USB_DEVICE;
@@ -81,6 +83,8 @@
             case AudioDeviceInfo.TYPE_DOCK:
             case AudioDeviceInfo.TYPE_DOCK_ANALOG:
             case AudioDeviceInfo.TYPE_HDMI:
+            case AudioDeviceInfo.TYPE_HDMI_ARC:
+            case AudioDeviceInfo.TYPE_HDMI_EARC:
             case AudioDeviceInfo.TYPE_USB_DEVICE:
                 return true;
             default:
diff --git a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
index eb997ba..8bc69c2 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
@@ -345,7 +345,7 @@
     }
 
     private void notifyBluetoothRoutesUpdated() {
-        mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes());
+        mListener.onBluetoothRoutesUpdated();
     }
 
     private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 93f6ff3..33190ad 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -231,7 +231,7 @@
             }
 
             if (isDeviceRouteChanged) {
-                mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute);
+                mOnDeviceRouteChangedListener.onDeviceRouteChanged();
             }
         }
     }
diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java
index ddeeacc..2b01001 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteController.java
@@ -136,12 +136,8 @@
      */
     interface BluetoothRoutesUpdatedListener {
 
-        /**
-         * Called when Bluetooth routes have changed.
-         *
-         * @param routes updated Bluetooth routes list.
-         */
-        void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes);
+        /** Called when Bluetooth routes have changed. */
+        void onBluetoothRoutesUpdated();
     }
 
     /**
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index e17f4a3..7876095 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -93,12 +93,8 @@
      */
     interface OnDeviceRouteChangedListener {
 
-        /**
-         * Called when device route has changed.
-         *
-         * @param deviceRoute non-null device route.
-         */
-        void onDeviceRouteChanged(@NonNull MediaRoute2Info deviceRoute);
+        /** Called when device route has changed. */
+        void onDeviceRouteChanged();
     }
 
 }
diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
index 1980403..ba3cecf 100644
--- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
@@ -280,7 +280,7 @@
 
     private void notifyBluetoothRoutesUpdated() {
         if (mListener != null) {
-            mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes());
+            mListener.onBluetoothRoutesUpdated();
         }
     }
 
diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
index 971d11f..6ba40ae 100644
--- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
@@ -165,8 +165,8 @@
         }
     }
 
-    private void notifyDeviceRouteUpdate(@NonNull MediaRoute2Info deviceRoute) {
-        mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute);
+    private void notifyDeviceRouteUpdate() {
+        mOnDeviceRouteChangedListener.onDeviceRouteChanged();
     }
 
     private class AudioRoutesObserver extends IAudioRoutesObserver.Stub {
@@ -177,7 +177,7 @@
             synchronized (LegacyDeviceRouteController.this) {
                 mDeviceRoute = deviceRoute;
             }
-            notifyDeviceRouteUpdate(deviceRoute);
+            notifyDeviceRouteUpdate();
         }
     }
 
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index c9528d8..9dec1df 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -63,6 +63,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.media.flags.Flags;
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
 
@@ -161,11 +162,13 @@
         mPowerManager = mContext.getSystemService(PowerManager.class);
         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
 
-        IntentFilter screenOnOffIntentFilter = new IntentFilter();
-        screenOnOffIntentFilter.addAction(ACTION_SCREEN_ON);
-        screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF);
+        if (!Flags.disableScreenOffBroadcastReceiver()) {
+            IntentFilter screenOnOffIntentFilter = new IntentFilter();
+            screenOnOffIntentFilter.addAction(ACTION_SCREEN_ON);
+            screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF);
+            mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter);
+        }
 
-        mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter);
         mContext.getPackageManager().addOnPermissionsChangeListener(this::onPermissionsChanged);
 
         MediaFeatureFlagManager.getInstance()
@@ -2779,7 +2782,8 @@
             List<ManagerRecord> managerRecords = getManagerRecords();
 
             boolean isManagerScanning = false;
-            if (service.mPowerManager.isInteractive()) {
+            if (Flags.disableScreenOffBroadcastReceiver()
+                    || service.mPowerManager.isInteractive()) {
                 isManagerScanning = managerRecords.stream().anyMatch(manager ->
                         manager.mIsScanning && service.mActivityManager
                                 .getPackageImportance(manager.mOwnerPackageName)
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/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 6c9aa4b..67a1ccd 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -109,21 +109,28 @@
 
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
 
-        mBluetoothRouteController = BluetoothRouteController.createInstance(context, (routes) -> {
-            publishProviderState();
-            if (updateSessionInfosIfNeeded()) {
-                notifySessionInfoUpdated();
-            }
-        });
+        mBluetoothRouteController =
+                BluetoothRouteController.createInstance(
+                        context,
+                        () -> {
+                            publishProviderState();
+                            if (updateSessionInfosIfNeeded()) {
+                                notifySessionInfoUpdated();
+                            }
+                        });
 
-        mDeviceRouteController = DeviceRouteController.createInstance(context, (deviceRoute) -> {
-            mHandler.post(() -> {
-                publishProviderState();
-                if (updateSessionInfosIfNeeded()) {
-                    notifySessionInfoUpdated();
-                }
-            });
-        });
+        mDeviceRouteController =
+                DeviceRouteController.createInstance(
+                        context,
+                        () -> {
+                            mHandler.post(
+                                    () -> {
+                                        publishProviderState();
+                                        if (updateSessionInfosIfNeeded()) {
+                                            notifySessionInfoUpdated();
+                                        }
+                                    });
+                        });
 
         mAudioManager.addOnDevicesForAttributesChangedListener(
                 AudioAttributesUtils.ATTRIBUTES_MEDIA, mContext.getMainExecutor(),
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/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 75a0cf5..6b7db2d 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -38,6 +38,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.media.AudioAttributes;
@@ -48,6 +49,7 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.VibrationEffect;
 import android.provider.Settings;
 import android.telephony.PhoneStateListener;
@@ -61,18 +63,21 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.EventLogTags;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
+import com.android.server.notification.Flags;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
@@ -87,15 +92,24 @@
     static final boolean DEBUG_INTERRUPTIVENESS = SystemProperties.getBoolean(
             "debug.notification.interruptiveness", false);
 
+    private static final float DEFAULT_VOLUME = 1.0f;
+    // TODO (b/291899544): remove for release
+    private static final String POLITE_STRATEGY1 = "rule1";
+    private static final String POLITE_STRATEGY2 = "rule2";
+    private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED = 1;
+    private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK = 0;
+    private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1;
+    private static final int DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED = 0;
+
     private final Context mContext;
     private final PackageManager mPackageManager;
     private final TelephonyManager mTelephonyManager;
+    private final UserManager mUm;
     private final NotificationManagerPrivate mNMP;
     private final SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver;
     private AccessibilityManager mAccessibilityManager;
     private KeyguardManager mKeyguardManager;
     private AudioManager mAudioManager;
-    private final LightsManager mLightsManager;
     private final NotificationUsageStats mUsageStats;
     private final ZenModeHelper mZenModeHelper;
 
@@ -126,17 +140,26 @@
     private final float mInCallNotificationVolume;
     private Binder mCallNotificationToken = null;
 
+    // Settings flags
+    private boolean mNotificationCooldownEnabled;
+    private boolean mNotificationCooldownForWorkEnabled;
+    private boolean mNotificationCooldownApplyToAll;
+    private boolean mNotificationCooldownVibrateUnlocked;
+
+    private boolean mEnablePoliteNotificationsFeature;
+    private final PolitenessStrategy mStrategy;
+    private int mCurrentWorkProfileId = UserHandle.USER_NULL;
 
     public NotificationAttentionHelper(Context context, LightsManager lightsManager,
             AccessibilityManager accessibilityManager, PackageManager packageManager,
-            NotificationUsageStats usageStats,
+            UserManager userManager, NotificationUsageStats usageStats,
             NotificationManagerPrivate notificationManagerPrivate,
             ZenModeHelper zenModeHelper, SystemUiSystemPropertiesFlags.FlagResolver flagResolver) {
         mContext = context;
         mPackageManager = packageManager;
         mTelephonyManager = context.getSystemService(TelephonyManager.class);
         mAccessibilityManager = accessibilityManager;
-        mLightsManager = lightsManager;
+        mUm = userManager;
         mNMP = notificationManagerPrivate;
         mUsageStats = usageStats;
         mZenModeHelper = zenModeHelper;
@@ -144,8 +167,8 @@
 
         mVibratorHelper = new VibratorHelper(context);
 
-        mNotificationLight = mLightsManager.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS);
-        mAttentionLight = mLightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION);
+        mNotificationLight = lightsManager.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS);
+        mAttentionLight = lightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION);
 
         Resources resources = context.getResources();
         mUseAttentionLight = resources.getBoolean(R.bool.config_useAttentionLight);
@@ -169,7 +192,39 @@
                 .build();
         mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume);
 
+        mEnablePoliteNotificationsFeature = Flags.politeNotifications();
+
+        if (mEnablePoliteNotificationsFeature) {
+            mStrategy = getPolitenessStrategy();
+        } else {
+            mStrategy = null;
+        }
+
         mSettingsObserver = new SettingsObserver();
+        loadUserSettings();
+    }
+
+    private PolitenessStrategy getPolitenessStrategy() {
+        final String politenessStrategy = mFlagResolver.getStringValue(
+                NotificationFlags.NOTIF_COOLDOWN_RULE);
+
+        if (POLITE_STRATEGY2.equals(politenessStrategy)) {
+            return new Strategy2(mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2));
+        } else {
+            if (!POLITE_STRATEGY1.equals(politenessStrategy)) {
+                Log.w(TAG, "Invalid cooldown strategy: " + politenessStrategy + ". Defaulting to "
+                        + POLITE_STRATEGY1);
+            }
+
+            return new Strategy1(mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
+        }
     }
 
     public void onSystemReady() {
@@ -202,11 +257,59 @@
         filter.addAction(Intent.ACTION_SCREEN_OFF);
         filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
         filter.addAction(Intent.ACTION_USER_PRESENT);
+        filter.addAction(Intent.ACTION_USER_ADDED);
+        filter.addAction(Intent.ACTION_USER_REMOVED);
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        filter.addAction(Intent.ACTION_USER_UNLOCKED);
         mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
 
         mContext.getContentResolver().registerContentObserver(
                 SettingsObserver.NOTIFICATION_LIGHT_PULSE_URI, false, mSettingsObserver,
                 UserHandle.USER_ALL);
+        if (mEnablePoliteNotificationsFeature) {
+            mContext.getContentResolver().registerContentObserver(
+                    SettingsObserver.NOTIFICATION_COOLDOWN_ENABLED_URI, false, mSettingsObserver,
+                    UserHandle.USER_ALL);
+            mContext.getContentResolver().registerContentObserver(
+                    SettingsObserver.NOTIFICATION_COOLDOWN_ALL_URI, false, mSettingsObserver,
+                    UserHandle.USER_ALL);
+            mContext.getContentResolver().registerContentObserver(
+                    SettingsObserver.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI, false,
+                    mSettingsObserver, UserHandle.USER_ALL);
+        }
+    }
+
+    private void loadUserSettings() {
+        if (mEnablePoliteNotificationsFeature) {
+            try {
+                mCurrentWorkProfileId = getManagedProfileId(ActivityManager.getCurrentUser());
+
+                mNotificationCooldownEnabled =
+                    Settings.System.getIntForUser(mContext.getContentResolver(),
+                        Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
+                        DEFAULT_NOTIFICATION_COOLDOWN_ENABLED, UserHandle.USER_CURRENT) != 0;
+                if (mCurrentWorkProfileId != UserHandle.USER_NULL) {
+                    mNotificationCooldownForWorkEnabled = Settings.System.getIntForUser(
+                        mContext.getContentResolver(),
+                        Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
+                        DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK, mCurrentWorkProfileId)
+                        != 0;
+                } else {
+                    mNotificationCooldownForWorkEnabled = false;
+                }
+                mNotificationCooldownApplyToAll = Settings.System.getIntForUser(
+                    mContext.getContentResolver(),
+                    Settings.System.NOTIFICATION_COOLDOWN_ALL, DEFAULT_NOTIFICATION_COOLDOWN_ALL,
+                    UserHandle.USER_CURRENT) != 0;
+                mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser(
+                    mContext.getContentResolver(),
+                    Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
+                    DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
+                    UserHandle.USER_CURRENT) != 0;
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to read Settings: " + e);
+            }
+        }
     }
 
     @VisibleForTesting
@@ -229,6 +332,10 @@
             Log.d(TAG, "buzzBeepBlinkLocked " + record);
         }
 
+        if (isPoliteNotificationFeatureEnabled(record)) {
+            mStrategy.onNotificationPosted(record);
+        }
+
         // Should this notification make noise, vibe, or use the LED?
         final boolean aboveThreshold =
                 mIsAutomotive
@@ -269,6 +376,9 @@
                     vibration = mVibratorHelper.createFallbackVibration(insistent);
                 }
                 hasValidVibrate = vibration != null;
+                // Vibration-only if unlocked and Settings flag set
+                boolean vibrateOnly =
+                        hasValidVibrate && mNotificationCooldownVibrateUnlocked && mUserPresent;
                 boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
                 if (hasAudibleAlert && !shouldMuteNotificationLocked(record, signals)) {
                     if (!sentAccessibilityEvent) {
@@ -277,7 +387,7 @@
                     }
                     if (DEBUG) Slog.v(TAG, "Interrupting!");
                     boolean isInsistentUpdate = isInsistentUpdate(record);
-                    if (hasValidSound) {
+                    if (hasValidSound && !vibrateOnly) {
                         if (isInsistentUpdate) {
                             // don't reset insistent sound, it's jarring
                             beep = true;
@@ -301,7 +411,7 @@
                         if (isInsistentUpdate) {
                             buzz = true;
                         } else {
-                            buzz = playVibration(record, vibration, hasValidSound);
+                            buzz = playVibration(record, vibration, hasValidSound && !vibrateOnly);
                             if (buzz) {
                                 mVibrateNotificationKey = key;
                             }
@@ -341,9 +451,7 @@
         } else if (wasShowLights) {
             updateLightsLocked();
         }
-        final int buzzBeepBlink =
-                (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0);
-        if (buzzBeepBlink > 0) {
+        if (buzz || beep || blink) {
             // Ignore summary updates because we don't display most of the information.
             if (record.getSbn().isGroup() && record.getSbn().getNotification().isGroupSummary()) {
                 if (DEBUG_INTERRUPTIVENESS) {
@@ -362,15 +470,43 @@
                             + record.getKey() + " is interruptive: alerted");
                 }
             }
+        }
+        final int buzzBeepBlinkLoggingCode =
+                (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0) | getPoliteBit(record);
+        if (buzzBeepBlinkLoggingCode > 0) {
             MetricsLogger.action(record.getLogMaker()
                     .setCategory(MetricsEvent.NOTIFICATION_ALERT)
                     .setType(MetricsEvent.TYPE_OPEN)
-                    .setSubtype(buzzBeepBlink));
-            EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
+                    .setSubtype(buzzBeepBlinkLoggingCode));
+            EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0,
+                    getPolitenessState(record));
         }
         record.setAudiblyAlerted(buzz || beep);
+        if (mEnablePoliteNotificationsFeature) {
+            // Update last alert time
+            if (buzz || beep) {
+                record.getChannel().setLastNotificationUpdateTimeMs(System.currentTimeMillis());
+            }
+        }
+        return buzzBeepBlinkLoggingCode;
+    }
 
-        return buzzBeepBlink;
+    private int getPoliteBit(final NotificationRecord record) {
+        switch (getPolitenessState(record)) {
+            case PolitenessStrategy.POLITE_STATE_POLITE:
+                return MetricsProto.MetricsEvent.ALERT_POLITE;
+            case PolitenessStrategy.POLITE_STATE_MUTED:
+                return MetricsProto.MetricsEvent.ALERT_MUTED;
+            default:
+                return 0;
+        }
+    }
+
+    private int getPolitenessState(final NotificationRecord record) {
+        if (!isPoliteNotificationFeatureEnabled(record)) {
+            return PolitenessStrategy.POLITE_STATE_DEFAULT;
+        }
+        return mStrategy.getPolitenessState(record);
     }
 
     boolean isInsistentUpdate(final NotificationRecord record) {
@@ -468,7 +604,7 @@
                                 + record.getAudioAttributes());
                     }
                     player.playAsync(soundUri, record.getSbn().getUser(), looping,
-                            record.getAudioAttributes());
+                            record.getAudioAttributes(), getSoundVolume(record));
                     return true;
                 }
             } catch (RemoteException e) {
@@ -480,12 +616,56 @@
         return false;
     }
 
+    private boolean isPoliteNotificationFeatureEnabled(final NotificationRecord record) {
+        // Check feature flag
+        if (!mEnablePoliteNotificationsFeature) {
+            return false;
+        }
+
+        // The user can enable/disable notifications cooldown from the Settings app
+        if (!mNotificationCooldownEnabled) {
+            return false;
+        }
+
+        // The user can enable/disable notifications cooldown for work profile from the Settings app
+        if (isNotificationForWorkProfile(record) && !mNotificationCooldownForWorkEnabled) {
+            return false;
+        }
+
+        // The user can choose to apply cooldown for all apps/conversations only from the
+        // Settings app
+        if (!mNotificationCooldownApplyToAll && record.getChannel().getConversationId() == null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private float getSoundVolume(final NotificationRecord record) {
+        if (!isPoliteNotificationFeatureEnabled(record)) {
+            return DEFAULT_VOLUME;
+        }
+
+        return mStrategy.getSoundVolume(record);
+    }
+
+    private float getVibrationIntensity(final NotificationRecord record) {
+        if (!isPoliteNotificationFeatureEnabled(record)) {
+            return DEFAULT_VOLUME;
+        }
+
+        return mStrategy.getVibrationIntensity(record);
+    }
+
     private boolean playVibration(final NotificationRecord record, final VibrationEffect effect,
             boolean delayVibForSound) {
         // Escalate privileges so we can use the vibrator even if the
         // notifying app does not have the VIBRATE permission.
         final long identity = Binder.clearCallingIdentity();
         try {
+            final float scale = getVibrationIntensity(record);
+            final VibrationEffect scaledEffect = Float.compare(scale, DEFAULT_VOLUME) != 0
+                    ? mVibratorHelper.scale(effect, scale) : effect;
             if (delayVibForSound) {
                 new Thread(() -> {
                     // delay the vibration by the same amount as the notification sound
@@ -503,7 +683,7 @@
                     // so need to check that the notification is still valid for vibrate.
                     if (mNMP.getNotificationByKey(record.getKey()) != null) {
                         if (record.getKey().equals(mVibrateNotificationKey)) {
-                            vibrate(record, effect, true);
+                            vibrate(record, scaledEffect, true);
                         } else {
                             if (DEBUG) {
                                 Slog.v(TAG, "No vibration for notification "
@@ -517,7 +697,7 @@
                     }
                 }).start();
             } else {
-                vibrate(record, effect, false);
+                vibrate(record, scaledEffect, false);
             }
             return true;
         } finally {
@@ -535,7 +715,7 @@
     }
 
     void playInCallNotification() {
-        // TODO: Should we apply politeness to mInCallNotificationVolume ?
+        // TODO b/270456865: Should we apply politeness to mInCallNotificationVolume ?
         final ContentResolver cr = mContext.getContentResolver();
         if (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_NORMAL
                 && Settings.Secure.getIntForUser(cr,
@@ -760,6 +940,22 @@
                 || signals.isCurrentProfile);
     }
 
+    private boolean isNotificationForWorkProfile(final NotificationRecord record) {
+        return (record.getUser().getIdentifier() == mCurrentWorkProfileId
+                && mCurrentWorkProfileId != UserHandle.USER_NULL);
+    }
+
+    private int getManagedProfileId(int parentUserId) {
+        final List<UserInfo> profiles = mUm.getProfiles(parentUserId);
+        for (UserInfo profile : profiles) {
+            if (profile.isManagedProfile()
+                    && profile.getUserHandle().getIdentifier() != parentUserId) {
+                return profile.getUserHandle().getIdentifier();
+            }
+        }
+        return UserHandle.USER_NULL;
+    }
+
     void sendAccessibilityEvent(NotificationRecord record) {
         if (!mAccessibilityManager.isEnabled()) {
             return;
@@ -791,6 +987,16 @@
         mAccessibilityManager.sendAccessibilityEvent(event);
     }
 
+    /**
+     * Notify the attention helper of a user interaction with a notification
+     * @param record that was interacted with
+     */
+    public void onUserInteraction(final NotificationRecord record) {
+        if (isPoliteNotificationFeatureEnabled(record)) {
+            mStrategy.onUserInteraction(record);
+        }
+    }
+
     public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
         pw.println("\n  Notification attention state:");
         pw.print(prefix);
@@ -834,6 +1040,243 @@
         }
     }
 
+    abstract private static class PolitenessStrategy {
+        static final int POLITE_STATE_DEFAULT = 0;
+        static final int POLITE_STATE_POLITE = 1;
+        static final int POLITE_STATE_MUTED = 2;
+
+        @IntDef(prefix = { "POLITE_STATE_" }, value = {
+                POLITE_STATE_DEFAULT,
+                POLITE_STATE_POLITE,
+                POLITE_STATE_MUTED,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface PolitenessState {}
+
+        protected final Map<String, Integer> mVolumeStates;
+
+        // Cooldown timer for transitioning into polite state
+        protected final int mTimeoutPolite;
+        // Cooldown timer for transitioning into muted state
+        protected final int mTimeoutMuted;
+        // Volume for polite state
+        protected final float mVolumePolite;
+        // Volume for muted state
+        protected final float mVolumeMuted;
+
+        public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite,
+                int volumeMuted) {
+            mVolumeStates = new HashMap<>();
+
+            this.mTimeoutPolite = timeoutPolite;
+            this.mTimeoutMuted = timeoutMuted;
+            this.mVolumePolite = volumePolite / 100.0f;
+            this.mVolumeMuted = volumeMuted / 100.0f;
+        }
+
+        abstract void onNotificationPosted(NotificationRecord record);
+
+        String getChannelKey(final NotificationRecord record) {
+            // use conversationId if it's a conversation
+            String channelId = record.getChannel().getConversationId() != null
+                    ? record.getChannel().getConversationId() : record.getChannel().getId();
+            return record.getSbn().getNormalizedUserId() + ":" + record.getSbn().getPackageName()
+                    + ":" + channelId;
+        }
+
+        public float getSoundVolume(final NotificationRecord record) {
+            float volume = DEFAULT_VOLUME;
+            final String key = getChannelKey(record);
+            final @PolitenessState int volState = getPolitenessState(record);
+
+            switch (volState) {
+                case POLITE_STATE_DEFAULT:
+                    volume = DEFAULT_VOLUME;
+                    break;
+                case POLITE_STATE_POLITE:
+                    volume = mVolumePolite;
+                    break;
+                case POLITE_STATE_MUTED:
+                    volume = mVolumeMuted;
+                    break;
+                default:
+                    Log.w(TAG, "getSoundVolume unexpected volume state: " + volState);
+                    break;
+            }
+
+            if (DEBUG) {
+                Log.i(TAG,
+                        "getSoundVolume state: " + volState + " vol: " + volume + " key: " + key);
+            }
+
+            return volume;
+        }
+
+        private float getVibrationIntensity(final NotificationRecord record) {
+            // TODO b/270456865: maybe use different scaling for vibration/sound ?
+            return getSoundVolume(record);
+        }
+
+        public void onUserInteraction(final NotificationRecord record) {
+            final String key = getChannelKey(record);
+            // reset to default state after user interaction
+            mVolumeStates.put(key, POLITE_STATE_DEFAULT);
+            record.getChannel().setLastNotificationUpdateTimeMs(0);
+        }
+
+        public final @PolitenessState int getPolitenessState(final NotificationRecord record) {
+            return mVolumeStates.getOrDefault(getChannelKey(record), POLITE_STATE_DEFAULT);
+        }
+    }
+
+    // TODO b/270456865: Only one of the two strategies will be released.
+    //  The other one need to be removed
+    /**
+     *  Polite notification strategy 1:
+     *   - Transitions from default (loud) => polite (lower volume) state if a notification
+     *  alerts the same channel before timeoutPolite.
+     *   - Transitions from polite => muted state if a notification alerts the same channel
+     *   before timeoutMuted OR transitions back to the default state if a notification alerts
+     *   after timeoutPolite.
+     *   - Transitions from muted => default state if the muted channel received more than maxPosted
+     *  notifications OR transitions back to the polite state if a notification alerts
+     *  after timeoutMuted.
+     *  - Transitions back to the default state after a user interaction with a notification.
+     */
+    public static class Strategy1 extends PolitenessStrategy {
+        // Keep track of the number of notifications posted per channel
+        private final Map<String, Integer> mNumPosted;
+        // Reset to default state if number of posted notifications exceed this value when muted
+        private final int mMaxPostedForReset;
+
+        public Strategy1(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted,
+                int maxPosted) {
+            super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
+
+            mNumPosted = new HashMap<>();
+            mMaxPostedForReset = maxPosted;
+
+            if (DEBUG) {
+                Log.i(TAG, "Strategy1: " + timeoutPolite + " " + timeoutMuted);
+            }
+        }
+
+        @Override
+        public void onNotificationPosted(final NotificationRecord record) {
+            long timeSinceLastNotif = System.currentTimeMillis()
+                    - record.getChannel().getLastNotificationUpdateTimeMs();
+
+            final String key = getChannelKey(record);
+            @PolitenessState int volState = getPolitenessState(record);
+
+            int numPosted = mNumPosted.getOrDefault(key, 0) + 1;
+            mNumPosted.put(key, numPosted);
+
+            switch (volState) {
+                case POLITE_STATE_DEFAULT:
+                    if (timeSinceLastNotif < mTimeoutPolite) {
+                        volState = POLITE_STATE_POLITE;
+                    }
+                    break;
+                case POLITE_STATE_POLITE:
+                    if (timeSinceLastNotif < mTimeoutMuted) {
+                        volState = POLITE_STATE_MUTED;
+                    } else if (timeSinceLastNotif > mTimeoutPolite) {
+                        volState = POLITE_STATE_DEFAULT;
+                    } else {
+                        volState = POLITE_STATE_POLITE;
+                    }
+                    break;
+                case POLITE_STATE_MUTED:
+                    if (timeSinceLastNotif > mTimeoutMuted) {
+                        volState = POLITE_STATE_POLITE;
+                    } else {
+                        volState = POLITE_STATE_MUTED;
+                    }
+                    if (numPosted >= mMaxPostedForReset) {
+                        volState = POLITE_STATE_DEFAULT;
+                        mNumPosted.put(key, 0);
+                    }
+                    break;
+                default:
+                    Log.w(TAG, "onNotificationPosted unexpected volume state: " + volState);
+                    break;
+            }
+
+            if (DEBUG) {
+                Log.i(TAG, "onNotificationPosted time delta: " + timeSinceLastNotif + " vol state: "
+                        + volState + " key: " + key + " numposted " + numPosted);
+            }
+
+            mVolumeStates.put(key, volState);
+        }
+
+        @Override
+        public void onUserInteraction(final NotificationRecord record) {
+            super.onUserInteraction(record);
+            mNumPosted.put(getChannelKey(record), 0);
+        }
+    }
+
+    /**
+     *  Polite notification strategy 2:
+     *   - Transitions from default (loud) => muted state if a notification
+     *   alerts the same channel before timeoutPolite.
+     *   - Transitions from polite => default state if a notification
+     *  alerts the same channel before timeoutMuted.
+     *   - Transitions from muted => default state if a notification alerts after timeoutMuted,
+     *  otherwise transitions to the polite state.
+     *   - Transitions back to the default state after a user interaction with a notification.
+     */
+    public static class Strategy2 extends PolitenessStrategy {
+        public Strategy2(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted) {
+            super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
+
+            if (DEBUG) {
+                Log.i(TAG, "Strategy2: " + timeoutPolite + " " + timeoutMuted);
+            }
+        }
+
+        @Override
+        public void onNotificationPosted(final NotificationRecord record) {
+            long timeSinceLastNotif = System.currentTimeMillis()
+                    - record.getChannel().getLastNotificationUpdateTimeMs();
+
+            final String key = getChannelKey(record);
+            @PolitenessState int volState = getPolitenessState(record);
+
+            switch (volState) {
+                case POLITE_STATE_DEFAULT:
+                    if (timeSinceLastNotif < mTimeoutPolite) {
+                        volState = POLITE_STATE_MUTED;
+                    }
+                    break;
+                case POLITE_STATE_POLITE:
+                    if (timeSinceLastNotif > mTimeoutMuted) {
+                        volState = POLITE_STATE_DEFAULT;
+                    }
+                    break;
+                case POLITE_STATE_MUTED:
+                    if (timeSinceLastNotif > mTimeoutMuted) {
+                        volState = POLITE_STATE_DEFAULT;
+                    } else {
+                        volState = POLITE_STATE_POLITE;
+                    }
+                    break;
+                default:
+                    Log.w(TAG, "onNotificationPosted unexpected volume state: " + volState);
+                    break;
+            }
+
+            if (DEBUG) {
+                Log.i(TAG, "onNotificationPosted time delta: " + timeSinceLastNotif + " vol state: "
+                        + volState + " key: " + key);
+            }
+
+            mVolumeStates.put(key, volState);
+        }
+    }
+
     //======================  Observers  =============================
     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
@@ -859,6 +1302,11 @@
                 if (mNotificationLight != null) {
                     mNotificationLight.turnOff();
                 }
+            } else if (action.equals(Intent.ACTION_USER_ADDED)
+                        || action.equals(Intent.ACTION_USER_REMOVED)
+                        || action.equals(Intent.ACTION_USER_SWITCHED)
+                        || action.equals(Intent.ACTION_USER_UNLOCKED)) {
+                loadUserSettings();
             }
         }
     };
@@ -867,6 +1315,12 @@
 
         private static final Uri NOTIFICATION_LIGHT_PULSE_URI = Settings.System.getUriFor(
                 Settings.System.NOTIFICATION_LIGHT_PULSE);
+        private static final Uri NOTIFICATION_COOLDOWN_ENABLED_URI = Settings.System.getUriFor(
+                Settings.System.NOTIFICATION_COOLDOWN_ENABLED);
+        private static final Uri NOTIFICATION_COOLDOWN_ALL_URI = Settings.System.getUriFor(
+                Settings.System.NOTIFICATION_COOLDOWN_ALL);
+        private static final Uri NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI =
+                Settings.System.getUriFor(Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED);
         public SettingsObserver() {
             super(null);
         }
@@ -884,11 +1338,45 @@
                     updateLightsLocked();
                 }
             }
+            if (mEnablePoliteNotificationsFeature) {
+                if (NOTIFICATION_COOLDOWN_ENABLED_URI.equals(uri)) {
+                    mNotificationCooldownEnabled = Settings.System.getIntForUser(
+                            mContext.getContentResolver(),
+                            Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
+                            DEFAULT_NOTIFICATION_COOLDOWN_ENABLED,
+                            UserHandle.USER_CURRENT) != 0;
+
+                    if (mCurrentWorkProfileId != UserHandle.USER_NULL) {
+                        mNotificationCooldownForWorkEnabled = Settings.System.getIntForUser(
+                                mContext.getContentResolver(),
+                                Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
+                                DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK,
+                                mCurrentWorkProfileId)
+                                != 0;
+                    } else {
+                        mNotificationCooldownForWorkEnabled = false;
+                    }
+                }
+                if (NOTIFICATION_COOLDOWN_ALL_URI.equals(uri)) {
+                    mNotificationCooldownApplyToAll = Settings.System.getIntForUser(
+                            mContext.getContentResolver(),
+                            Settings.System.NOTIFICATION_COOLDOWN_ALL,
+                            DEFAULT_NOTIFICATION_COOLDOWN_ALL, UserHandle.USER_CURRENT)
+                            != 0;
+                }
+                if (NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI.equals(uri)) {
+                    mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser(
+                            mContext.getContentResolver(),
+                            Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
+                            DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
+                            UserHandle.USER_CURRENT) != 0;
+                }
+            }
         }
     }
 
 
-    //TODO: cleanup most (all?) of these
+    // TODO b/270456865: cleanup most (all?) of these
     //======================= FOR TESTS =====================
     @VisibleForTesting
     void setIsAutomotive(boolean isAutomotive) {
@@ -931,6 +1419,11 @@
     }
 
     @VisibleForTesting
+    void setUserPresent(boolean userPresent) {
+        mUserPresent = userPresent;
+    }
+
+    @VisibleForTesting
     void setLights(LogicalLight light) {
         mNotificationLight = light;
         mAttentionLight = light;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 87c3067..837b761 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2532,7 +2532,7 @@
 
         if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
             mAttentionHelper = new NotificationAttentionHelper(getContext(), lightsManager,
-                mAccessibilityManager, mPackageManagerClient, usageStats,
+                mAccessibilityManager, mPackageManagerClient, userManager, usageStats,
                 mNotificationManagerPrivate, mZenModeHelper, flagResolver);
         }
 
@@ -3369,6 +3369,10 @@
         mAppUsageStats.reportEvent(r.getSbn().getPackageName(),
                 getRealUserId(r.getSbn().getUserId()),
                 UsageEvents.Event.USER_INTERACTION);
+
+        if (Flags.politeNotifications()) {
+            mAttentionHelper.onUserInteraction(r);
+        }
     }
 
     private int getRealUserId(int userId) {
@@ -5730,13 +5734,18 @@
         public void setNotificationListenerAccessGrantedForUser(ComponentName listener, int userId,
                 boolean granted, boolean userSet) {
             Objects.requireNonNull(listener);
+            if (UserHandle.getCallingUserId() != userId) {
+                getContext().enforceCallingOrSelfPermission(
+                        android.Manifest.permission.INTERACT_ACROSS_USERS,
+                        "setNotificationListenerAccessGrantedForUser for user " + userId);
+            }
             checkNotificationListenerAccess();
             if (granted && listener.flattenToString().length()
                     > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) {
                 throw new IllegalArgumentException(
                         "Component name too long: " + listener.flattenToString());
             }
-            if (!userSet && isNotificationListenerAccessUserSet(listener)) {
+            if (!userSet && isNotificationListenerAccessUserSet(listener, userId)) {
                 // Don't override user's choice
                 return;
             }
@@ -5762,9 +5771,8 @@
             }
         }
 
-        private boolean isNotificationListenerAccessUserSet(ComponentName listener) {
-            return mListeners.isPackageOrComponentUserSet(listener.flattenToString(),
-                    getCallingUserHandle().getIdentifier());
+        private boolean isNotificationListenerAccessUserSet(ComponentName listener, int userId) {
+            return mListeners.isPackageOrComponentUserSet(listener.flattenToString(), userId);
         }
 
         @Override
@@ -8612,7 +8620,7 @@
                     .setCategory(MetricsEvent.NOTIFICATION_ALERT)
                     .setType(MetricsEvent.TYPE_OPEN)
                     .setSubtype(buzzBeepBlink));
-            EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
+            EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0, 0);
         }
         record.setAudiblyAlerted(buzz || beep);
         return buzzBeepBlink;
@@ -8757,7 +8765,7 @@
                     if (DBG) Slog.v(TAG, "Playing sound " + soundUri
                             + " with attributes " + record.getAudioAttributes());
                     player.playAsync(soundUri, record.getSbn().getUser(), looping,
-                            record.getAudioAttributes());
+                            record.getAudioAttributes(), 1.0f);
                     return true;
                 }
             } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java
index e5d07bc..7204d05 100644
--- a/services/core/java/com/android/server/notification/VibratorHelper.java
+++ b/services/core/java/com/android/server/notification/VibratorHelper.java
@@ -50,6 +50,7 @@
     private final long[] mFallbackPattern;
     @Nullable private final float[] mDefaultPwlePattern;
     @Nullable private final float[] mFallbackPwlePattern;
+    private final int mDefaultVibrationAmplitude;
 
     public VibratorHelper(Context context) {
         mVibrator = context.getSystemService(Vibrator.class);
@@ -65,6 +66,8 @@
                 com.android.internal.R.array.config_defaultNotificationVibeWaveform);
         mFallbackPwlePattern = getFloatArray(context.getResources(),
                 com.android.internal.R.array.config_notificationFallbackVibeWaveform);
+        mDefaultVibrationAmplitude = context.getResources().getInteger(
+            com.android.internal.R.integer.config_defaultVibrationAmplitude);
     }
 
     /**
@@ -136,6 +139,14 @@
     }
 
     /**
+     *  Scale vibration effect, valid range is [0.0f, 1.0f]
+     *  Resolves default amplitude value if not already set.
+     */
+    public VibrationEffect scale(VibrationEffect effect, float scale) {
+        return effect.resolve(mDefaultVibrationAmplitude).scale(scale);
+    }
+
+    /**
      * Vibrate the device with given {@code effect}.
      *
      * <p>We need to vibrate as "android" so we can breakthrough DND.
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/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index f987629..79cd2a0 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -489,6 +489,9 @@
 
     boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId);
 
+    /** Check if the package is in a stopped state for a given user. */
+    boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId);
+
     boolean isSuspendingAnyPackages(@NonNull String suspendingPackage, @UserIdInt int userId);
 
     @NonNull
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 5d2944e..7db7bf5 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -4938,7 +4938,7 @@
             int userId) {
         final int callingUid = Binder.getCallingUid();
         enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
-                false /* checkShell */, "isPackageSuspendedForUser for user " + userId);
+                false /* checkShell */, "when asking about packages for user " + userId);
         final PackageStateInternal ps = mSettings.getPackage(packageName);
         if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)) {
             throw new IllegalArgumentException("Unknown target package: " + packageName);
@@ -4957,6 +4957,11 @@
     }
 
     @Override
+    public boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId) {
+        return getUserStageOrDefaultForUser(packageName, userId).isStopped();
+    }
+
+    @Override
     public boolean isSuspendingAnyPackages(@NonNull String suspendingPackage,
             @UserIdInt int userId) {
         for (final PackageStateInternal packageState : getPackageStates().values()) {
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index 76203ac..9a0306b 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -961,6 +961,12 @@
     }
 
     @Override
+    public final boolean isPackageStoppedForUser(@NonNull String packageName,
+            @UserIdInt int userId) {
+        return snapshot().isPackageStoppedForUser(packageName, userId);
+    }
+
+    @Override
     @Deprecated
     public final boolean isSafeMode() {
         // allow instant applications
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index c6388e7..edb45aa 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.Flags.preventSdkLibApp;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
@@ -994,10 +995,11 @@
                         return;
                     }
                     final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0;
-                    if (!isApex) {
-                        createdAppId.put(packageName, optimisticallyRegisterAppId(request));
-                    } else {
+                    final boolean isSdkLibrary = packageToScan.isSdkLibrary();
+                    if (isApex || (isSdkLibrary && preventSdkLibApp())) {
                         request.getScannedPackageSetting().setAppId(Process.INVALID_UID);
+                    } else {
+                        createdAppId.put(packageName, optimisticallyRegisterAppId(request));
                     }
                     versionInfos.put(packageName,
                             mPm.getSettingsVersionForPackage(packageToScan));
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 1c7024b..7d822b5 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -34,6 +34,7 @@
 import android.app.AppOpsManager;
 import android.content.pm.ArchivedPackageParcel;
 import android.content.pm.DataLoaderType;
+import android.content.pm.Flags;
 import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
@@ -922,7 +923,7 @@
         // Only report external profile warnings when installing from adb. The goal is to warn app
         // developers if they have provided bad external profiles, so it's not beneficial to report
         // those warnings in the normal app install workflow.
-        if (isInstallFromAdb()) {
+        if (isInstallFromAdb() && Flags.useArtServiceV2()) {
             var externalProfileErrors = new LinkedHashSet<String>();
             for (PackageDexoptResult packageResult : dexoptResult.getPackageDexoptResults()) {
                 for (DexContainerFileDexoptResult fileResult :
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 11660a59..a161e8c 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -107,19 +107,22 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.Preconditions;
+import com.android.internal.util.SizedInputStream;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
+import java.io.DataInputStream;
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
 import java.nio.file.attribute.PosixFilePermission;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -130,6 +133,8 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.function.BiConsumer;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
@@ -216,6 +221,7 @@
         private final ShortcutChangeHandler mShortcutChangeHandler;
 
         private final Handler mCallbackHandler;
+        private final ExecutorService mOnDumpExecutor = Executors.newSingleThreadExecutor();
 
         private PackageInstallerService mPackageInstallerService;
 
@@ -1512,7 +1518,7 @@
                     forEachViewCaptureWindow((fileName, is) -> {
                         try {
                             zipOs.putNextEntry(new ZipEntry("FS" + fileName));
-                            is.transferTo(zipOs);
+                            transferViewCaptureData(is, zipOs);
                             zipOs.closeEntry();
                         } catch (IOException e) {
                             getErrPrintWriter().write("Failed to output " + fileName
@@ -1553,8 +1559,9 @@
         private void dumpViewCaptureDataToWmTrace(@NonNull String fileName,
                 @NonNull InputStream is) {
             Path outPath = Paths.get(fileName);
-            try {
-                Files.copy(is, outPath, StandardCopyOption.REPLACE_EXISTING);
+            try (OutputStream os = Files.newOutputStream(outPath, StandardOpenOption.CREATE,
+                    StandardOpenOption.TRUNCATE_EXISTING)) {
+                transferViewCaptureData(is, os);
                 Files.setPosixFilePermissions(outPath, WM_TRACE_FILE_PERMISSIONS);
             } catch (IOException e) {
                 Log.d(TAG, "failed to write data to " + fileName + " in wmtrace dir", e);
@@ -1562,6 +1569,15 @@
         }
 
         /**
+         * Raw input stream reads hang on the final read when transferring data in via the pipe.
+         * The fix used below is to count and read the exact amount of bytes being sent.
+         */
+        private void transferViewCaptureData(InputStream is, OutputStream os) throws IOException {
+            DataInputStream dataInputStream = new DataInputStream(is);
+            new SizedInputStream(dataInputStream, dataInputStream.readInt()).transferTo(os);
+        }
+
+        /**
          * IDumpCallback.onDump alerts the in-process ViewCapture instance to start sending data
          * to LauncherAppsService via the pipe's input provided. This data (as well as an output
          * file name) is provided to the consumer via an InputStream to output where it wants (for
@@ -1569,24 +1585,37 @@
          */
         private void forEachViewCaptureWindow(
                 @NonNull BiConsumer<String, InputStream> outputtingConsumer) {
-            for (int i = mDumpCallbacks.beginBroadcast() - 1; i >= 0; i--) {
-                String packageName = (String) mDumpCallbacks.getBroadcastCookie(i);
-                String fileName = WM_TRACE_DIR + packageName + "_" + i + VC_FILE_SUFFIX;
+            try {
+                // This multi-threading prevents ctrl-C command line command aborting from putting
+                // the mDumpCallbacks RemoteCallbackList in a bad Broadcast state. We need to wait
+                // for it to complete even though it is on a background thread.
+                mOnDumpExecutor.submit(() -> {
+                    try {
+                        for (int i = mDumpCallbacks.beginBroadcast() - 1; i >= 0; i--) {
+                            String packageName = (String) mDumpCallbacks.getBroadcastCookie(i);
+                            String fileName = WM_TRACE_DIR + packageName + "_" + i + VC_FILE_SUFFIX;
 
-                try {
-                    // Order is important here. OnDump needs to be called before the BiConsumer
-                    // accepts & starts blocking on reading the input stream.
-                    ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
-                    mDumpCallbacks.getBroadcastItem(i).onDump(pipe[1]);
+                            try {
+                                // Order is important here. OnDump needs to be called before the
+                                // BiConsumer accepts & starts blocking on reading the input stream.
+                                ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+                                mDumpCallbacks.getBroadcastItem(i).onDump(pipe[1]);
 
-                    InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0]);
-                    outputtingConsumer.accept(fileName, is);
-                    is.close();
-                } catch (Exception e) {
-                    Log.d(TAG, "failed to pipe view capture data", e);
-                }
+                                InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(
+                                        pipe[0]);
+                                outputtingConsumer.accept(fileName, is);
+                                is.close();
+                            } catch (Exception e) {
+                                Log.d(TAG, "failed to pipe view capture data", e);
+                            }
+                        }
+                    } finally {
+                        mDumpCallbacks.finishBroadcast();
+                    }
+                }).get();
+            } catch (InterruptedException | ExecutionException e) {
+                Log.e(TAG, "background work was interrupted", e);
             }
-            mDumpCallbacks.finishBroadcast();
         }
 
         @RequiresPermission(READ_FRAME_BUFFER)
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ddc8369..68aa93d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4501,6 +4501,7 @@
             boolean stopped, @UserIdInt int userId) {
         if (!mUserManager.exists(userId)) return;
         final int callingUid = Binder.getCallingUid();
+        boolean wasStopped = false;
         if (snapshot.getInstantAppPackageName(callingUid) == null) {
             final int permission = mContext.checkCallingOrSelfPermission(
                     Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
@@ -4522,6 +4523,7 @@
                     ? null : packageState.getUserStateOrDefault(userId);
             if (packageState != null && packageUserState.isStopped() != stopped) {
                 boolean wasNotLaunched = packageUserState.isNotLaunched();
+                wasStopped = packageUserState.isStopped();
                 commitPackageStateMutation(null, packageName, state -> {
                     PackageUserStateWrite userState = state.userState(userId);
                     userState.setStopped(stopped);
@@ -4553,6 +4555,24 @@
                     ah.setHibernatingGlobally(packageName, false);
                 }
             });
+            // Send UNSTOPPED broadcast if necessary
+            if (wasStopped && Flags.stayStopped()) {
+                final PackageManagerInternal pmi =
+                        mInjector.getLocalService(PackageManagerInternal.class);
+                final int [] userIds = resolveUserIds(userId);
+                final SparseArray<int[]> broadcastAllowList =
+                        snapshotComputer().getVisibilityAllowLists(packageName, userIds);
+                final Bundle extras = new Bundle();
+                extras.putInt(Intent.EXTRA_UID, pmi.getPackageUid(packageName, 0, userId));
+                extras.putInt(Intent.EXTRA_USER_HANDLE, userId);
+                mHandler.post(() -> {
+                    mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_UNSTOPPED,
+                            packageName, extras,
+                            Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null,
+                            userIds, null, broadcastAllowList, null,
+                            null);
+                });
+            }
         }
     }
 
@@ -6929,6 +6949,25 @@
         public ParceledListSlice<PackageInstaller.SessionInfo> getHistoricalSessions(int userId) {
             return mInstallerService.getHistoricalSessions(userId);
         }
+
+        @Override
+        public void sendPackageRestartedBroadcast(@NonNull String packageName,
+                int uid, @Intent.Flags int flags) {
+            final int userId = UserHandle.getUserId(uid);
+            final int [] userIds = resolveUserIds(userId);
+            final SparseArray<int[]> broadcastAllowList =
+                    snapshotComputer().getVisibilityAllowLists(packageName, userIds);
+            final Bundle extras = new Bundle();
+            extras.putInt(Intent.EXTRA_UID, uid);
+            extras.putInt(Intent.EXTRA_USER_HANDLE, userId);
+            mHandler.post(() -> {
+                mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_RESTARTED,
+                        packageName, extras,
+                        flags, null, null,
+                        userIds, null, broadcastAllowList, null,
+                        null);
+            });
+        }
     }
 
     private void setEnabledOverlayPackages(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 68a8e40..7331bc1 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -75,13 +75,13 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.multiuser.Flags;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.Environment;
 import android.os.FileUtils;
-import android.os.Flags;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IProgressListener;
@@ -1335,18 +1335,6 @@
                 && user.profileGroupId == profile.profileGroupId);
     }
 
-    private Intent buildProfileAvailabilityIntent(UserInfo profile, boolean enableQuietMode,
-            boolean useManagedActions) {
-        Intent intent = new Intent();
-        intent.setAction(getAvailabilityIntentAction(enableQuietMode, useManagedActions));
-        intent.putExtra(Intent.EXTRA_QUIET_MODE, enableQuietMode);
-        intent.putExtra(Intent.EXTRA_USER, profile.getUserHandle());
-        intent.putExtra(Intent.EXTRA_USER_HANDLE, profile.getUserHandle().getIdentifier());
-        intent.addFlags(
-                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
-        return intent;
-    }
-
     private String getAvailabilityIntentAction(boolean enableQuietMode, boolean useManagedActions) {
         return useManagedActions ?
                 enableQuietMode ?
@@ -1359,12 +1347,20 @@
 
     private void broadcastProfileAvailabilityChanges(UserInfo profileInfo,
             UserHandle parentHandle, boolean enableQuietMode, boolean useManagedActions) {
-        Intent availabilityIntent = buildProfileAvailabilityIntent(profileInfo, enableQuietMode,
-                useManagedActions);
+        Intent availabilityIntent = new Intent();
+        availabilityIntent.setAction(
+                getAvailabilityIntentAction(enableQuietMode, useManagedActions));
+        availabilityIntent.putExtra(Intent.EXTRA_QUIET_MODE, enableQuietMode);
+        availabilityIntent.putExtra(Intent.EXTRA_USER, profileInfo.getUserHandle());
+        availabilityIntent.putExtra(Intent.EXTRA_USER_HANDLE,
+                profileInfo.getUserHandle().getIdentifier());
         if (profileInfo.isManagedProfile()) {
             getDevicePolicyManagerInternal().broadcastIntentToManifestReceivers(
                     availabilityIntent, parentHandle, /* requiresPermission= */ true);
         }
+        availabilityIntent.addFlags(
+                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+
         // TODO(b/302708423): Restrict the apps that can receive these intents in case of a private
         //  profile.
         final Bundle options = new BroadcastOptions()
@@ -1559,7 +1555,7 @@
         logQuietModeEnabled(userId, enableQuietMode, callingPackage);
 
         // Broadcast generic intents for all profiles
-        if (Flags.allowPrivateProfile()) {
+        if (android.os.Flags.allowPrivateProfile()) {
             broadcastProfileAvailabilityChanges(profile, parent.getUserHandle(),
                     enableQuietMode, false);
         }
@@ -3785,6 +3781,8 @@
 
     @GuardedBy({"mPackagesLock"})
     private void readUserListLP() {
+        // Whether guest restrictions are present on userlist.xml
+        boolean guestRestrictionsArePresentOnUserListXml = false;
         try (ResilientAtomicFile file = getUserListFile()) {
             FileInputStream fin = null;
             try {
@@ -3834,6 +3832,7 @@
                                 }
                             }
                         } else if (name.equals(TAG_GUEST_RESTRICTIONS)) {
+                            guestRestrictionsArePresentOnUserListXml = true;
                             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                                     && type != XmlPullParser.END_TAG) {
                                 if (type == XmlPullParser.START_TAG) {
@@ -3852,6 +3851,7 @@
 
                 updateUserIds();
                 upgradeIfNecessaryLP();
+                updateUsersWithFeatureFlags(guestRestrictionsArePresentOnUserListXml);
             } catch (Exception e) {
                 // Remove corrupted file and retry.
                 file.failRead(fin, e);
@@ -3877,6 +3877,24 @@
     }
 
     /**
+     * Update any user formats or Xml data that need to be updated based on the current user state
+     * and the feature flag settings.
+     */
+    @GuardedBy({"mPackagesLock"})
+    private void updateUsersWithFeatureFlags(boolean guestRestrictionsArePresentOnUserListXml) {
+        // User Xml re-writes are required when guest restrictions are saved on userlist.xml but
+        // as per the feature flag it should be on the SYSTEM user's xml or guest restrictions
+        // are saved on SYSTEM user's xml but as per the flags it should not be saved there.
+        if (guestRestrictionsArePresentOnUserListXml
+                == Flags.saveGlobalAndGuestRestrictionsOnSystemUserXmlReadOnly()) {
+            for (int userId: getUserIds()) {
+                writeUserLP(getUserDataNoChecks(userId));
+            }
+            writeUserListLP();
+        }
+    }
+
+    /**
      * Version of {@link #upgradeIfNecessaryLP()} that takes in the userVersion for testing
      * purposes. For non-tests, use {@link #upgradeIfNecessaryLP()}.
      */
@@ -4393,9 +4411,24 @@
             UserRestrictionsUtils.writeRestrictions(serializer,
                     mBaseUserRestrictions.getRestrictions(userInfo.id), TAG_RESTRICTIONS);
 
-            UserRestrictionsUtils.writeRestrictions(serializer,
-                    mDevicePolicyUserRestrictions.getRestrictions(UserHandle.USER_ALL),
-                    TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS);
+            if (Flags.saveGlobalAndGuestRestrictionsOnSystemUserXmlReadOnly()) {
+                if (userInfo.id == UserHandle.USER_SYSTEM) {
+                    UserRestrictionsUtils.writeRestrictions(serializer,
+                            mDevicePolicyUserRestrictions.getRestrictions(UserHandle.USER_ALL),
+                            TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS);
+
+                    serializer.startTag(null, TAG_GUEST_RESTRICTIONS);
+                    synchronized (mGuestRestrictions) {
+                        UserRestrictionsUtils.writeRestrictions(serializer, mGuestRestrictions,
+                                TAG_RESTRICTIONS);
+                    }
+                    serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
+                }
+            } else {
+                UserRestrictionsUtils.writeRestrictions(serializer,
+                        mDevicePolicyUserRestrictions.getRestrictions(UserHandle.USER_ALL),
+                        TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS);
+            }
 
             UserRestrictionsUtils.writeRestrictions(serializer,
                     mDevicePolicyUserRestrictions.getRestrictions(userInfo.id),
@@ -4471,12 +4504,15 @@
                 serializer.attributeInt(null, ATTR_USER_VERSION, mUserVersion);
                 serializer.attributeInt(null, ATTR_USER_TYPE_VERSION, mUserTypeVersion);
 
-                serializer.startTag(null, TAG_GUEST_RESTRICTIONS);
-                synchronized (mGuestRestrictions) {
-                    UserRestrictionsUtils
-                            .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
+                if (!Flags.saveGlobalAndGuestRestrictionsOnSystemUserXmlReadOnly()) {
+                    serializer.startTag(null, TAG_GUEST_RESTRICTIONS);
+                    synchronized (mGuestRestrictions) {
+                        UserRestrictionsUtils
+                                .writeRestrictions(serializer, mGuestRestrictions,
+                                        TAG_RESTRICTIONS);
+                    }
+                    serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
                 }
-                serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
                 int[] userIdsToWrite;
                 synchronized (mUsersLock) {
                     userIdsToWrite = new int[mUsers.size()];
@@ -4620,6 +4656,19 @@
                     localRestrictions = UserRestrictionsUtils.readRestrictions(parser);
                 } else if (TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS.equals(tag)) {
                     globalRestrictions = UserRestrictionsUtils.readRestrictions(parser);
+                } else if (TAG_GUEST_RESTRICTIONS.equals(tag)) {
+                    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                            && type != XmlPullParser.END_TAG) {
+                        if (type == XmlPullParser.START_TAG) {
+                            if (parser.getName().equals(TAG_RESTRICTIONS)) {
+                                synchronized (mGuestRestrictions) {
+                                    UserRestrictionsUtils
+                                            .readRestrictions(parser, mGuestRestrictions);
+                                }
+                            }
+                            break;
+                        }
+                    }
                 } else if (TAG_ACCOUNT.equals(tag)) {
                     type = parser.next();
                     if (type == XmlPullParser.TEXT) {
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 85b60a0..29e0c35 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -295,6 +295,8 @@
                         .setCredentialShareableWithParent(false)
                         .setMediaSharedWithParent(false)
                         .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
+                        .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
+                        .setHideInSettingsInQuietMode(true)
                         .setCrossProfileIntentFilterAccessControl(
                                 UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
                         .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT));
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/pm/pkg/SuspendParams.java b/services/core/java/com/android/server/pm/pkg/SuspendParams.java
index 86391c9..153238fa 100644
--- a/services/core/java/com/android/server/pm/pkg/SuspendParams.java
+++ b/services/core/java/com/android/server/pm/pkg/SuspendParams.java
@@ -17,7 +17,6 @@
 package com.android.server.pm.pkg;
 
 import android.annotation.Nullable;
-import android.content.pm.Flags;
 import android.content.pm.SuspendDialogInfo;
 import android.os.BaseBundle;
 import android.os.PersistableBundle;
@@ -142,8 +141,7 @@
         PersistableBundle readAppExtras = null;
         PersistableBundle readLauncherExtras = null;
 
-        final boolean quarantined = in.getAttributeBoolean(null, ATTR_QUARANTINED, false)
-                && Flags.quarantinedEnabled();
+        final boolean quarantined = in.getAttributeBoolean(null, ATTR_QUARANTINED, false);
 
         final int currentDepth = in.getDepth();
         int type;
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index f14941b..46121dc 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.Flags.preventSdkLibApp;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
@@ -403,8 +404,9 @@
 
         try {
             final File baseApk = new File(lite.getBaseApkPath());
+            boolean shouldSkipComponents = lite.isIsSdkLibrary() && preventSdkLibApp();
             final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
-                    lite.getPath(), assetLoader, flags);
+                    lite.getPath(), assetLoader, flags, shouldSkipComponents);
             if (result.isError()) {
                 return input.error(result);
             }
@@ -456,10 +458,11 @@
         final PackageLite lite = liteResult.getResult();
         final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
         try {
+            boolean shouldSkipComponents =  lite.isIsSdkLibrary() && preventSdkLibApp();
             final ParseResult<ParsingPackage> result = parseBaseApk(input,
                     apkFile,
                     apkFile.getCanonicalPath(),
-                    assetLoader, flags);
+                    assetLoader, flags, shouldSkipComponents);
             if (result.isError()) {
                 return input.error(result);
             }
@@ -594,7 +597,8 @@
     }
 
     private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,
-            String codePath, SplitAssetLoader assetLoader, int flags) {
+            String codePath, SplitAssetLoader assetLoader, int flags,
+            boolean shouldSkipComponents) {
         final String apkPath = apkFile.getAbsolutePath();
 
         final String volumeUuid = getVolumeUuid(apkPath);
@@ -619,7 +623,7 @@
             final Resources res = new Resources(assets, mDisplayMetrics, null);
 
             ParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res,
-                    parser, flags);
+                    parser, flags, shouldSkipComponents);
             if (result.isError()) {
                 return input.error(result.getErrorCode(),
                         apkPath + " (at " + parser.getPositionDescription() + "): "
@@ -719,11 +723,12 @@
      * @param res     The resources from which to resolve values
      * @param parser  The manifest parser
      * @param flags   Flags how to parse
+     * @param shouldSkipComponents If the package is a sdk-library
      * @return Parsed package or null on error.
      */
     private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath,
-            String codePath, Resources res, XmlResourceParser parser, int flags)
-            throws XmlPullParserException, IOException {
+            String codePath, Resources res, XmlResourceParser parser, int flags,
+            boolean shouldSkipComponents) throws XmlPullParserException, IOException {
         final String splitName;
         final String pkgName;
 
@@ -751,7 +756,8 @@
             final ParsingPackage pkg = mCallback.startParsingPackage(
                     pkgName, apkPath, codePath, manifestArray, isCoreApp);
             final ParseResult<ParsingPackage> result =
-                    parseBaseApkTags(input, pkg, manifestArray, res, parser, flags);
+                    parseBaseApkTags(input, pkg, manifestArray, res, parser, flags,
+                            shouldSkipComponents);
             if (result.isError()) {
                 return result;
             }
@@ -987,10 +993,9 @@
                 return ParsingUtils.unknownTag("<application>", pkg, parser, input);
         }
     }
-
     private ParseResult<ParsingPackage> parseBaseApkTags(ParseInput input, ParsingPackage pkg,
-            TypedArray sa, Resources res, XmlResourceParser parser, int flags)
-            throws XmlPullParserException, IOException {
+            TypedArray sa, Resources res, XmlResourceParser parser, int flags,
+            boolean shouldSkipComponents) throws XmlPullParserException, IOException {
         ParseResult<ParsingPackage> sharedUserResult = parseSharedUser(input, pkg, sa);
         if (sharedUserResult.isError()) {
             return sharedUserResult;
@@ -1027,7 +1032,8 @@
                     }
                 } else {
                     foundApp = true;
-                    result = parseBaseApplication(input, pkg, res, parser, flags);
+                    result = parseBaseApplication(input, pkg, res, parser, flags,
+                            shouldSkipComponents);
                 }
             } else {
                 result = parseBaseApkTag(tagName, input, pkg, res, parser, flags);
@@ -1972,8 +1978,8 @@
      * code moves around.
      */
     private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input,
-            ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
-            throws XmlPullParserException, IOException {
+            ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags,
+            boolean shouldSkipComponents) throws XmlPullParserException, IOException {
         final String pkgName = pkg.getPackageName();
         int targetSdk = pkg.getTargetSdkVersion();
 
@@ -2213,6 +2219,9 @@
                     isActivity = true;
                     // fall-through
                 case "receiver":
+                    if (shouldSkipComponents) {
+                        continue;
+                    }
                     ParseResult<ParsedActivity> activityResult =
                             ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
                                     res, parser, flags, sUseRoundIcon, null /*defaultSplitName*/,
@@ -2232,6 +2241,9 @@
                     result = activityResult;
                     break;
                 case "service":
+                    if (shouldSkipComponents) {
+                        continue;
+                    }
                     ParseResult<ParsedService> serviceResult =
                             ParsedServiceUtils.parseService(mSeparateProcesses, pkg, res, parser,
                                     flags, sUseRoundIcon, null /*defaultSplitName*/,
@@ -2245,6 +2257,9 @@
                     result = serviceResult;
                     break;
                 case "provider":
+                    if (shouldSkipComponents) {
+                        continue;
+                    }
                     ParseResult<ParsedProvider> providerResult =
                             ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
                                     flags, sUseRoundIcon, null /*defaultSplitName*/,
@@ -2256,6 +2271,9 @@
                     result = providerResult;
                     break;
                 case "activity-alias":
+                    if (shouldSkipComponents) {
+                        continue;
+                    }
                     activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res,
                             parser, sUseRoundIcon, null /*defaultSplitName*/,
                             input);
@@ -2414,7 +2432,7 @@
     /**
      * For parsing non-MainComponents. Main ones have an order and some special handling which is
      * done directly in {@link #parseBaseApplication(ParseInput, ParsingPackage, Resources,
-     * XmlResourceParser, int)}.
+     * XmlResourceParser, int, boolean)}.
      */
     private ParseResult parseBaseAppChildTag(ParseInput input, String tag, ParsingPackage pkg,
             Resources res, XmlResourceParser parser, int flags)
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 097656c..3a6664a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -333,6 +333,11 @@
     static final int SHORT_PRESS_SLEEP_GO_TO_SLEEP = 0;
     static final int SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME = 1;
 
+    // must match: config_shortPressOnSettingsBehavior in config.xml
+    static final int SHORT_PRESS_SETTINGS_NOTHING = 0;
+    static final int SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL = 1;
+    static final int LAST_SHORT_PRESS_SETTINGS_BEHAVIOR = SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL;
+
     static final int PENDING_KEY_NULL = -1;
 
     // Must match: config_shortPressOnStemPrimaryBehavior in config.xml
@@ -611,6 +616,9 @@
     // What we do when the user double-taps on home
     int mDoubleTapOnHomeBehavior;
 
+    // What we do when the user presses on settings
+    int mShortPressOnSettingsBehavior;
+
     // Must match config_primaryShortPressTargetActivity in config.xml
     ComponentName mPrimaryShortPressTargetActivity;
 
@@ -2766,6 +2774,13 @@
         if (mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
             mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE;
         }
+
+        mShortPressOnSettingsBehavior = res.getInteger(
+                com.android.internal.R.integer.config_shortPressOnSettingsBehavior);
+        if (mShortPressOnSettingsBehavior < SHORT_PRESS_SETTINGS_NOTHING
+                || mShortPressOnSettingsBehavior > LAST_SHORT_PRESS_SETTINGS_BEHAVIOR) {
+            mShortPressOnSettingsBehavior = SHORT_PRESS_SETTINGS_NOTHING;
+        }
     }
 
     private void updateSettings() {
@@ -3632,6 +3647,15 @@
                 Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in"
                         + " interceptKeyBeforeQueueing");
                 return true;
+            case KeyEvent.KEYCODE_SETTINGS:
+                if (mShortPressOnSettingsBehavior == SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL) {
+                    if (!down) {
+                        toggleNotificationPanel();
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
+                    }
+                    return true;
+                }
+                break;
         }
         if (isValidGlobalKey(keyCode)
                 && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
@@ -6272,6 +6296,9 @@
                 pw.print("mLongPressOnPowerBehavior=");
                 pw.println(longPressOnPowerBehaviorToString(mLongPressOnPowerBehavior));
         pw.print(prefix);
+        pw.print("mShortPressOnSettingsBehavior=");
+        pw.println(shortPressOnSettingsBehaviorToString(mShortPressOnSettingsBehavior));
+        pw.print(prefix);
         pw.print("mLongPressOnPowerAssistantTimeoutMs=");
         pw.println(mLongPressOnPowerAssistantTimeoutMs);
         pw.print(prefix);
@@ -6470,6 +6497,17 @@
         }
     }
 
+    private static String shortPressOnSettingsBehaviorToString(int behavior) {
+        switch (behavior) {
+            case SHORT_PRESS_SETTINGS_NOTHING:
+                return "SHORT_PRESS_SETTINGS_NOTHING";
+            case SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL:
+                return "SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL";
+            default:
+                return Integer.toString(behavior);
+        }
+    }
+
     private static String veryLongPressOnPowerBehaviorToString(int behavior) {
         switch (behavior) {
             case VERY_LONG_PRESS_POWER_NOTHING:
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
index 6cc9d0a..bc90f5c 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
@@ -18,15 +18,26 @@
 
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
+import android.os.BatteryConsumer;
 import android.os.UserHandle;
 import android.text.format.DateFormat;
 import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.TimeUtils;
 
 import com.android.internal.os.PowerStats;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
 
-import java.io.PrintWriter;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -34,25 +45,75 @@
  * etc) covering a specific period of power usage history.
  */
 class AggregatedPowerStats {
+    private static final String TAG = "AggregatedPowerStats";
+    private static final int MAX_CLOCK_UPDATES = 100;
+    private static final String XML_TAG_AGGREGATED_POWER_STATS = "agg-power-stats";
+
     private final PowerComponentAggregatedPowerStats[] mPowerComponentStats;
 
-    @CurrentTimeMillisLong
-    private long mStartTime;
+    static class ClockUpdate {
+        public long monotonicTime;
+        @CurrentTimeMillisLong public long currentTime;
+    }
+
+    private final List<ClockUpdate> mClockUpdates = new ArrayList<>();
 
     @DurationMillisLong
     private long mDurationMs;
 
-    AggregatedPowerStats(PowerComponentAggregatedPowerStats... powerComponentAggregatedPowerStats) {
-        this.mPowerComponentStats = powerComponentAggregatedPowerStats;
+    AggregatedPowerStats(AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
+        List<AggregatedPowerStatsConfig.PowerComponent> configs =
+                aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs();
+        mPowerComponentStats = new PowerComponentAggregatedPowerStats[configs.size()];
+        for (int i = 0; i < configs.size(); i++) {
+            mPowerComponentStats[i] = createPowerComponentAggregatedPowerStats(configs.get(i));
+        }
     }
 
-    void setStartTime(@CurrentTimeMillisLong long startTime) {
-        mStartTime = startTime;
+    private PowerComponentAggregatedPowerStats createPowerComponentAggregatedPowerStats(
+            AggregatedPowerStatsConfig.PowerComponent config) {
+        switch (config.getPowerComponentId()) {
+            case BatteryConsumer.POWER_COMPONENT_CPU:
+                return new CpuAggregatedPowerStats(config);
+            default:
+                return new PowerComponentAggregatedPowerStats(config);
+        }
     }
 
-    @CurrentTimeMillisLong
-    public long getStartTime() {
-        return mStartTime;
+    /**
+     * Records a mapping of monotonic time to wall-clock time. Since wall-clock time can change,
+     * there may be multiple clock updates in one set of aggregated stats.
+     *
+     * @param monotonicTime monotonic time in milliseconds, see
+     * {@link com.android.internal.os.MonotonicClock}
+     * @param currentTime   current time in milliseconds, see {@link System#currentTimeMillis()}
+     */
+    void addClockUpdate(long monotonicTime, @CurrentTimeMillisLong long currentTime) {
+        ClockUpdate clockUpdate = new ClockUpdate();
+        clockUpdate.monotonicTime = monotonicTime;
+        clockUpdate.currentTime = currentTime;
+        if (mClockUpdates.size() < MAX_CLOCK_UPDATES) {
+            mClockUpdates.add(clockUpdate);
+        } else {
+            Slog.i(TAG, "Too many clock updates. Replacing the previous update with "
+                        + DateFormat.format("yyyy-MM-dd-HH-mm-ss", currentTime));
+            mClockUpdates.set(mClockUpdates.size() - 1, clockUpdate);
+        }
+    }
+
+    /**
+     * Start time according to {@link com.android.internal.os.MonotonicClock}
+     */
+    long getStartTime() {
+        if (mClockUpdates.isEmpty()) {
+            return 0;
+        } else {
+            return mClockUpdates.get(0).monotonicTime;
+        }
+    }
+
+    List<ClockUpdate> getClockUpdates() {
+        return mClockUpdates;
     }
 
     void setDuration(long durationMs) {
@@ -73,13 +134,14 @@
         return null;
     }
 
-    void setDeviceState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) {
+    void setDeviceState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state,
+            long time) {
         for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
             stats.setState(stateId, state, time);
         }
     }
 
-    void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state,
+    void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state,
             long time) {
         for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
             stats.setUidState(uid, stateId, state, time);
@@ -106,20 +168,90 @@
     }
 
     void reset() {
-        mStartTime = 0;
+        mClockUpdates.clear();
         mDurationMs = 0;
         for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
             stats.reset();
         }
     }
 
-    void dump(PrintWriter pw) {
-        IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
-        ipw.print("Start time: ");
-        ipw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartTime));
-        ipw.print(" duration: ");
-        ipw.print(mDurationMs);
-        ipw.println();
+    public void writeXml(TypedXmlSerializer serializer) throws IOException {
+        serializer.startTag(null, XML_TAG_AGGREGATED_POWER_STATS);
+        for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+            stats.writeXml(serializer);
+        }
+        serializer.endTag(null, XML_TAG_AGGREGATED_POWER_STATS);
+        serializer.flush();
+    }
+
+    @NonNull
+    public static AggregatedPowerStats createFromXml(
+            TypedXmlPullParser parser, AggregatedPowerStatsConfig aggregatedPowerStatsConfig)
+            throws XmlPullParserException, IOException {
+        AggregatedPowerStats stats = new AggregatedPowerStats(aggregatedPowerStatsConfig);
+        boolean inElement = false;
+        boolean skipToEnd = false;
+        int eventType = parser.getEventType();
+        while (eventType != XmlPullParser.END_DOCUMENT
+                   && !(eventType == XmlPullParser.END_TAG
+                        && parser.getName().equals(XML_TAG_AGGREGATED_POWER_STATS))) {
+            if (!skipToEnd && eventType == XmlPullParser.START_TAG) {
+                switch (parser.getName()) {
+                    case XML_TAG_AGGREGATED_POWER_STATS:
+                        inElement = true;
+                        break;
+                    case PowerComponentAggregatedPowerStats.XML_TAG_POWER_COMPONENT:
+                        if (!inElement) {
+                            break;
+                        }
+
+                        int powerComponentId = parser.getAttributeInt(null,
+                                PowerComponentAggregatedPowerStats.XML_ATTR_ID);
+                        for (PowerComponentAggregatedPowerStats powerComponent :
+                                stats.mPowerComponentStats) {
+                            if (powerComponent.powerComponentId == powerComponentId) {
+                                if (!powerComponent.readFromXml(parser)) {
+                                    skipToEnd = true;
+                                }
+                                break;
+                            }
+                        }
+                        break;
+                }
+            }
+            eventType = parser.next();
+        }
+        return stats;
+    }
+
+    void dump(IndentingPrintWriter ipw) {
+        StringBuilder sb = new StringBuilder();
+        long baseTime = 0;
+        for (int i = 0; i < mClockUpdates.size(); i++) {
+            ClockUpdate clockUpdate = mClockUpdates.get(i);
+            sb.setLength(0);
+            if (i == 0) {
+                baseTime = clockUpdate.monotonicTime;
+                sb.append("Start time: ")
+                        .append(DateFormat.format("yyyy-MM-dd-HH-mm-ss", clockUpdate.currentTime))
+                        .append(" (")
+                        .append(baseTime)
+                        .append(") duration: ")
+                        .append(mDurationMs);
+                ipw.println(sb);
+            } else {
+                sb.setLength(0);
+                sb.append("Clock update:  ");
+                TimeUtils.formatDuration(
+                        clockUpdate.monotonicTime - baseTime, sb,
+                        TimeUtils.HUNDRED_DAY_FIELD_LEN + 3);
+                sb.append(" ").append(
+                        DateFormat.format("yyyy-MM-dd-HH-mm-ss", clockUpdate.currentTime));
+                ipw.increaseIndent();
+                ipw.println(sb);
+                ipw.decreaseIndent();
+            }
+        }
 
         ipw.println("Device");
         ipw.increaseIndent();
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
new file mode 100644
index 0000000..477c228
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
@@ -0,0 +1,156 @@
+/*
+ * 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.power.stats;
+
+import android.annotation.IntDef;
+import android.os.BatteryConsumer;
+
+import com.android.internal.os.MultiStateStats;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Configuration that controls how power stats are aggregated.  It determines which state changes
+ * are to be considered as essential dimensions ("tracked states") for each power component (CPU,
+ * WiFi, etc).  Also, it determines which states are tracked globally and which ones on a per-UID
+ * basis.
+ */
+public class AggregatedPowerStatsConfig {
+    public static final int STATE_POWER = 0;
+    public static final int STATE_SCREEN = 1;
+    public static final int STATE_PROCESS_STATE = 2;
+
+    @IntDef({
+            STATE_POWER,
+            STATE_SCREEN,
+            STATE_PROCESS_STATE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TrackedState {
+    }
+
+    static final String STATE_NAME_POWER = "pwr";
+    static final int POWER_STATE_BATTERY = 0;
+    static final int POWER_STATE_OTHER = 1;   // Plugged in, or on wireless charger, etc.
+    static final String[] STATE_LABELS_POWER = {"pwr-battery", "pwr-other"};
+
+    static final String STATE_NAME_SCREEN = "scr";
+    static final int SCREEN_STATE_ON = 0;
+    static final int SCREEN_STATE_OTHER = 1;  // Off, doze etc
+    static final String[] STATE_LABELS_SCREEN = {"scr-on", "scr-other"};
+
+    static final String STATE_NAME_PROCESS_STATE = "ps";
+    static final String[] STATE_LABELS_PROCESS_STATE;
+
+    static {
+        String[] procStateLabels = new String[BatteryConsumer.PROCESS_STATE_COUNT];
+        for (int i = 0; i < BatteryConsumer.PROCESS_STATE_COUNT; i++) {
+            procStateLabels[i] = BatteryConsumer.processStateToString(i);
+        }
+        STATE_LABELS_PROCESS_STATE = procStateLabels;
+    }
+
+    /**
+     * Configuration for a give power component (CPU, WiFi, etc)
+     */
+    public static class PowerComponent {
+        private final int mPowerComponentId;
+        private @TrackedState int[] mTrackedDeviceStates;
+        private @TrackedState int[] mTrackedUidStates;
+
+        PowerComponent(int powerComponentId) {
+            this.mPowerComponentId = powerComponentId;
+        }
+
+        /**
+         * Configures which states should be tracked as separate dimensions for the entire device.
+         */
+        public PowerComponent trackDeviceStates(@TrackedState int... states) {
+            mTrackedDeviceStates = states;
+            return this;
+        }
+
+        /**
+         * Configures which states should be tracked as separate dimensions on a per-UID basis.
+         */
+        public PowerComponent trackUidStates(@TrackedState int... states) {
+            mTrackedUidStates = states;
+            return this;
+        }
+
+        public int getPowerComponentId() {
+            return mPowerComponentId;
+        }
+
+        public MultiStateStats.States[] getDeviceStateConfig() {
+            return new MultiStateStats.States[]{
+                    new MultiStateStats.States(STATE_NAME_POWER,
+                            isTracked(mTrackedDeviceStates, STATE_POWER),
+                            STATE_LABELS_POWER),
+                    new MultiStateStats.States(STATE_NAME_SCREEN,
+                            isTracked(mTrackedDeviceStates, STATE_SCREEN),
+                            STATE_LABELS_SCREEN),
+            };
+        }
+
+        public MultiStateStats.States[] getUidStateConfig() {
+            return new MultiStateStats.States[]{
+                    new MultiStateStats.States(STATE_NAME_POWER,
+                            isTracked(mTrackedUidStates, STATE_POWER),
+                            AggregatedPowerStatsConfig.STATE_LABELS_POWER),
+                    new MultiStateStats.States(STATE_NAME_SCREEN,
+                            isTracked(mTrackedUidStates, STATE_SCREEN),
+                            AggregatedPowerStatsConfig.STATE_LABELS_SCREEN),
+                    new MultiStateStats.States(STATE_NAME_PROCESS_STATE,
+                            isTracked(mTrackedUidStates, STATE_PROCESS_STATE),
+                            AggregatedPowerStatsConfig.STATE_LABELS_PROCESS_STATE),
+            };
+        }
+
+        private boolean isTracked(int[] trackedStates, int state) {
+            if (trackedStates == null) {
+                return false;
+            }
+
+            for (int trackedState : trackedStates) {
+                if (trackedState == state) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    private final List<PowerComponent> mPowerComponents = new ArrayList<>();
+
+    /**
+     * Creates a configuration for the specified power component, which may be one of the
+     * standard power component IDs, e.g. {@link BatteryConsumer#POWER_COMPONENT_CPU}, or
+     * a custom power component.
+     */
+    public PowerComponent trackPowerComponent(int powerComponentId) {
+        PowerComponent builder = new PowerComponent(powerComponentId);
+        mPowerComponents.add(builder);
+        return builder;
+    }
+
+    public List<PowerComponent> getPowerComponentsAggregatedStatsConfigs() {
+        return mPowerComponents;
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java
new file mode 100644
index 0000000..7ba4330
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.util.IndentingPrintWriter;
+
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+
+class AggregatedPowerStatsSection extends PowerStatsSpan.Section {
+    public static final String TYPE = "aggregated-power-stats";
+
+    private final AggregatedPowerStats mAggregatedPowerStats;
+
+    AggregatedPowerStatsSection(AggregatedPowerStats aggregatedPowerStats) {
+        super(TYPE);
+        mAggregatedPowerStats = aggregatedPowerStats;
+    }
+
+    public AggregatedPowerStats getAggregatedPowerStats() {
+        return mAggregatedPowerStats;
+    }
+
+    @Override
+    void write(TypedXmlSerializer serializer) throws IOException {
+        mAggregatedPowerStats.writeXml(serializer);
+    }
+
+    @Override
+    public void dump(IndentingPrintWriter ipw) {
+        mAggregatedPowerStats.dump(ipw);
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 613f189..a9c2bc2 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -124,6 +124,7 @@
 import com.android.internal.os.KernelSingleUidTimeReader;
 import com.android.internal.os.LongArrayMultiStateCounter;
 import com.android.internal.os.LongMultiStateCounter;
+import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
 import com.android.internal.os.RailStats;
@@ -283,7 +284,6 @@
     private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
     private int[] mCpuPowerBracketMap;
     private final CpuPowerStatsCollector mCpuPowerStatsCollector;
-    private final PowerStatsAggregator mPowerStatsAggregator;
 
     public LongSparseArray<SamplingTimer> getKernelMemoryStats() {
         return mKernelMemoryStats;
@@ -356,6 +356,11 @@
     protected Queue<UidToRemove> mPendingRemovedUids = new LinkedList<>();
 
     @NonNull
+    public BatteryStatsHistory getHistory() {
+        return mHistory;
+    }
+
+    @NonNull
     BatteryStatsHistory copyHistory() {
         return mHistory.copy();
     }
@@ -1718,14 +1723,7 @@
         return mMaxLearnedBatteryCapacityUah;
     }
 
-    public BatteryStatsImpl() {
-        this(Clock.SYSTEM_CLOCK);
-    }
-
-    public BatteryStatsImpl(Clock clock) {
-        this(clock, null);
-    }
-
+    @VisibleForTesting
     public BatteryStatsImpl(Clock clock, File historyDirectory) {
         init(clock);
         mBatteryStatsConfig = new BatteryStatsConfig.Builder().build();
@@ -1737,18 +1735,19 @@
             mCheckinFile = null;
             mStatsFile = null;
             mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
-                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock,
+                    new MonotonicClock(0, mClock));
         } else {
             mCheckinFile = new AtomicFile(new File(historyDirectory, "batterystats-checkin.bin"));
             mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin"));
             mHistory = new BatteryStatsHistory(historyDirectory, mConstants.MAX_HISTORY_FILES,
-                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock,
+                    new MonotonicClock(0, mClock));
         }
         mPlatformIdleStateCallback = null;
         mEnergyConsumerRetriever = null;
         mUserInfoProvider = null;
         mCpuPowerStatsCollector = null;
-        mPowerStatsAggregator = null;
     }
 
     private void init(Clock clock) {
@@ -10906,20 +10905,12 @@
         return mTmpCpuTimeInFreq;
     }
 
-    public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @Nullable File systemDir,
+    public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock,
+            @NonNull MonotonicClock monotonicClock, @Nullable File systemDir,
             @NonNull Handler handler, @Nullable PlatformIdleStateCallback cb,
             @Nullable EnergyStatsRetriever energyStatsCb,
             @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile,
             @NonNull CpuScalingPolicies cpuScalingPolicies) {
-        this(config, Clock.SYSTEM_CLOCK, systemDir, handler, cb, energyStatsCb, userInfoProvider,
-                powerProfile, cpuScalingPolicies);
-    }
-
-    private BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock,
-            @Nullable File systemDir, @NonNull Handler handler,
-            @Nullable PlatformIdleStateCallback cb, @Nullable EnergyStatsRetriever energyStatsCb,
-            @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile,
-            @NonNull CpuScalingPolicies cpuScalingPolicies) {
         init(clock);
 
         mBatteryStatsConfig = config;
@@ -10936,31 +10927,19 @@
             mCheckinFile = null;
             mDailyFile = null;
             mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
-                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock);
         } else {
             mStatsFile = new AtomicFile(new File(systemDir, "batterystats.bin"));
             mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
             mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
             mHistory = new BatteryStatsHistory(systemDir, mConstants.MAX_HISTORY_FILES,
-                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock);
         }
 
         mCpuPowerStatsCollector = new CpuPowerStatsCollector(mCpuScalingPolicies, mPowerProfile,
                 mHandler, mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu());
         mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
 
-        PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory);
-        builder.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
-                .trackDeviceStates(
-                        PowerStatsAggregator.STATE_POWER,
-                        PowerStatsAggregator.STATE_SCREEN)
-                .trackUidStates(
-                        PowerStatsAggregator.STATE_POWER,
-                        PowerStatsAggregator.STATE_SCREEN,
-                        PowerStatsAggregator.STATE_PROCESS_STATE);
-
-        mPowerStatsAggregator = builder.build();
-
         mStartCount++;
         initTimersAndCounters();
         mOnBattery = mOnBatteryInternal = false;
@@ -15702,20 +15681,23 @@
     }
 
     /**
+     * Schedules an immediate (but asynchronous) collection of PowerStats samples.
+     * Callers will need to wait for the collection to complete on the handler thread.
+     */
+    public void schedulePowerStatsSampleCollection() {
+        if (mCpuPowerStatsCollector == null) {
+            return;
+        }
+        mCpuPowerStatsCollector.forceSchedule();
+    }
+
+    /**
      * Grabs one sample of PowerStats and prints it.
      */
     public void dumpStatsSample(PrintWriter pw) {
         mCpuPowerStatsCollector.collectAndDump(pw);
     }
 
-    /**
-     * Aggregates power stats between the specified times and prints them.
-     */
-    public void dumpAggregatedStats(PrintWriter pw, long startTimeMs, long endTimeMs) {
-        mPowerStatsAggregator.aggregateBatteryStats(startTimeMs, endTimeMs,
-                stats-> stats.dump(pw));
-    }
-
     private final Runnable mWriteAsyncRunnable = () -> {
         synchronized (BatteryStatsImpl.this) {
             writeSyncLocked();
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index f6fa9f2..851a3f7 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -46,7 +46,7 @@
     private static final String TAG = "BatteryUsageStatsProv";
     private final Context mContext;
     private final BatteryStats mStats;
-    private final BatteryUsageStatsStore mBatteryUsageStatsStore;
+    private final PowerStatsStore mPowerStatsStore;
     private final PowerProfile mPowerProfile;
     private final CpuScalingPolicies mCpuScalingPolicies;
     private final Object mLock = new Object();
@@ -58,10 +58,10 @@
 
     @VisibleForTesting
     public BatteryUsageStatsProvider(Context context, BatteryStats stats,
-            BatteryUsageStatsStore batteryUsageStatsStore) {
+            PowerStatsStore powerStatsStore) {
         mContext = context;
         mStats = stats;
-        mBatteryUsageStatsStore = batteryUsageStatsStore;
+        mPowerStatsStore = powerStatsStore;
         mPowerProfile = stats instanceof BatteryStatsImpl
                 ? ((BatteryStatsImpl) stats).getPowerProfile()
                 : new PowerProfile(context);
@@ -314,20 +314,52 @@
         final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
                 customEnergyConsumerNames, includePowerModels, includeProcessStateData,
                 minConsumedPowerThreshold);
-        if (mBatteryUsageStatsStore == null) {
-            Log.e(TAG, "BatteryUsageStatsStore is unavailable");
+        if (mPowerStatsStore == null) {
+            Log.e(TAG, "PowerStatsStore is unavailable");
             return builder.build();
         }
 
-        final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps();
-        for (long timestamp : timestamps) {
-            if (timestamp > query.getFromTimestamp() && timestamp <= query.getToTimestamp()) {
-                final BatteryUsageStats snapshot =
-                        mBatteryUsageStatsStore.loadBatteryUsageStats(timestamp);
-                if (snapshot == null) {
-                    continue;
-                }
+        List<PowerStatsSpan.Metadata> toc = mPowerStatsStore.getTableOfContents();
+        for (PowerStatsSpan.Metadata spanMetadata : toc) {
+            if (!spanMetadata.getSections().contains(BatteryUsageStatsSection.TYPE)) {
+                continue;
+            }
 
+            // BatteryUsageStatsQuery is expressed in terms of wall-clock time range for the
+            // session end time.
+            //
+            // The following algorithm is correct when there is only one time frame in the span.
+            // When the wall-clock time is adjusted in the middle of an stats span,
+            // constraining it by wall-clock time becomes ambiguous. In this case, the algorithm
+            // only covers some situations, but not others.  When using the resulting data for
+            // analysis, we should always pay attention to the full set of included timeframes.
+            // TODO(b/298459065): switch to monotonic clock
+            long minTime = Long.MAX_VALUE;
+            long maxTime = 0;
+            for (PowerStatsSpan.TimeFrame timeFrame : spanMetadata.getTimeFrames()) {
+                long spanEndTime = timeFrame.startTime + timeFrame.duration;
+                minTime = Math.min(minTime, spanEndTime);
+                maxTime = Math.max(maxTime, spanEndTime);
+            }
+
+            // Per BatteryUsageStatsQuery API, the "from" timestamp is *exclusive*,
+            // while the "to" timestamp is *inclusive*.
+            boolean isInRange =
+                    (query.getFromTimestamp() == 0 || minTime > query.getFromTimestamp())
+                    && (query.getToTimestamp() == 0 || maxTime <= query.getToTimestamp());
+            if (!isInRange) {
+                continue;
+            }
+
+            PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan(
+                    spanMetadata.getId(), BatteryUsageStatsSection.TYPE);
+            if (powerStatsSpan == null) {
+                continue;
+            }
+
+            for (PowerStatsSpan.Section section : powerStatsSpan.getSections()) {
+                BatteryUsageStats snapshot =
+                        ((BatteryUsageStatsSection) section).getBatteryUsageStats();
                 if (!Arrays.equals(snapshot.getCustomPowerComponentNames(),
                         customEnergyConsumerNames)) {
                     Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different "
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
new file mode 100644
index 0000000..b95faac
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.os.BatteryUsageStats;
+import android.util.IndentingPrintWriter;
+
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+
+class BatteryUsageStatsSection extends PowerStatsSpan.Section {
+    public static final String TYPE = "battery-usage-stats";
+
+    private final BatteryUsageStats mBatteryUsageStats;
+
+    BatteryUsageStatsSection(BatteryUsageStats batteryUsageStats) {
+        super(TYPE);
+        mBatteryUsageStats = batteryUsageStats;
+    }
+
+    public BatteryUsageStats getBatteryUsageStats() {
+        return mBatteryUsageStats;
+    }
+
+    @Override
+    void write(TypedXmlSerializer serializer) throws IOException {
+        mBatteryUsageStats.writeXml(serializer);
+    }
+
+    @Override
+    public void dump(IndentingPrintWriter ipw) {
+        mBatteryUsageStats.dump(ipw, "");
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java
deleted file mode 100644
index 0d7a140..0000000
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.power.stats;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.os.BatteryUsageStats;
-import android.os.BatteryUsageStatsQuery;
-import android.os.Handler;
-import android.util.AtomicFile;
-import android.util.Log;
-import android.util.LongArray;
-import android.util.Slog;
-import android.util.Xml;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.StandardOpenOption;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-import java.util.TreeMap;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * A storage mechanism for BatteryUsageStats snapshots.
- */
-public class BatteryUsageStatsStore {
-    private static final String TAG = "BatteryUsageStatsStore";
-
-    private static final List<BatteryUsageStatsQuery> BATTERY_USAGE_STATS_QUERY = List.of(
-            new BatteryUsageStatsQuery.Builder()
-                    .setMaxStatsAgeMs(0)
-                    .includePowerModels()
-                    .includeProcessStateData()
-                    .build());
-    private static final String BATTERY_USAGE_STATS_DIR = "battery-usage-stats";
-    private static final String SNAPSHOT_FILE_EXTENSION = ".bus";
-    private static final String DIR_LOCK_FILENAME = ".lock";
-    private static final String CONFIG_FILENAME = "config";
-    private static final String BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY =
-            "BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP";
-    private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 100 * 1024;
-
-    private final Context mContext;
-    private final BatteryStatsImpl mBatteryStats;
-    private boolean mSystemReady;
-    private final File mStoreDir;
-    private final File mLockFile;
-    private final ReentrantLock mFileLock = new ReentrantLock();
-    private FileLock mJvmLock;
-    private final AtomicFile mConfigFile;
-    private final long mMaxStorageBytes;
-    private final Handler mHandler;
-    private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
-
-    public BatteryUsageStatsStore(Context context, BatteryStatsImpl stats, File systemDir,
-            Handler handler) {
-        this(context, stats, systemDir, handler, MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
-    }
-
-    @VisibleForTesting
-    public BatteryUsageStatsStore(Context context, BatteryStatsImpl batteryStats, File systemDir,
-            Handler handler, long maxStorageBytes) {
-        mContext = context;
-        mBatteryStats = batteryStats;
-        mStoreDir = new File(systemDir, BATTERY_USAGE_STATS_DIR);
-        mLockFile = new File(mStoreDir, DIR_LOCK_FILENAME);
-        mConfigFile = new AtomicFile(new File(mStoreDir, CONFIG_FILENAME));
-        mHandler = handler;
-        mMaxStorageBytes = maxStorageBytes;
-        mBatteryStats.setBatteryResetListener(this::prepareForBatteryStatsReset);
-        mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(mContext, mBatteryStats);
-    }
-
-    /**
-     * Notifies BatteryUsageStatsStore that the system server is ready.
-     */
-    public void onSystemReady() {
-        mSystemReady = true;
-    }
-
-    private void prepareForBatteryStatsReset(int resetReason) {
-        if (resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE || !mSystemReady) {
-            return;
-        }
-
-        final List<BatteryUsageStats> stats =
-                mBatteryUsageStatsProvider.getBatteryUsageStats(BATTERY_USAGE_STATS_QUERY);
-        if (stats.isEmpty()) {
-            Slog.wtf(TAG, "No battery usage stats generated");
-            return;
-        }
-
-        mHandler.post(() -> storeBatteryUsageStats(stats.get(0)));
-    }
-
-    private void storeBatteryUsageStats(BatteryUsageStats stats) {
-        lockSnapshotDirectory();
-        try {
-            if (!mStoreDir.exists()) {
-                if (!mStoreDir.mkdirs()) {
-                    Slog.e(TAG, "Could not create a directory for battery usage stats snapshots");
-                    return;
-                }
-            }
-            File file = makeSnapshotFilename(stats.getStatsEndTimestamp());
-            try {
-                writeXmlFileLocked(stats, file);
-            } catch (Exception e) {
-                Slog.e(TAG, "Cannot save battery usage stats", e);
-            }
-
-            removeOldSnapshotsLocked();
-        } finally {
-            unlockSnapshotDirectory();
-        }
-    }
-
-    /**
-     * Returns the timestamps of the stored BatteryUsageStats snapshots. The timestamp corresponds
-     * to the time the snapshot was taken {@link BatteryUsageStats#getStatsEndTimestamp()}.
-     */
-    public long[] listBatteryUsageStatsTimestamps() {
-        LongArray timestamps = new LongArray(100);
-        lockSnapshotDirectory();
-        try {
-            for (File file : mStoreDir.listFiles()) {
-                String fileName = file.getName();
-                if (fileName.endsWith(SNAPSHOT_FILE_EXTENSION)) {
-                    try {
-                        String fileNameWithoutExtension = fileName.substring(0,
-                                fileName.length() - SNAPSHOT_FILE_EXTENSION.length());
-                        timestamps.add(Long.parseLong(fileNameWithoutExtension));
-                    } catch (NumberFormatException e) {
-                        Slog.wtf(TAG, "Invalid format of BatteryUsageStats snapshot file name: "
-                                + fileName);
-                    }
-                }
-            }
-        } finally {
-            unlockSnapshotDirectory();
-        }
-        return timestamps.toArray();
-    }
-
-    /**
-     * Reads the specified snapshot of BatteryUsageStats.  Returns null if the snapshot
-     * does not exist.
-     */
-    @Nullable
-    public BatteryUsageStats loadBatteryUsageStats(long timestamp) {
-        lockSnapshotDirectory();
-        try {
-            File file = makeSnapshotFilename(timestamp);
-            try {
-                return readXmlFileLocked(file);
-            } catch (Exception e) {
-                Slog.e(TAG, "Cannot read battery usage stats", e);
-            }
-        } finally {
-            unlockSnapshotDirectory();
-        }
-        return null;
-    }
-
-    /**
-     * Saves the supplied timestamp of the BATTERY_USAGE_STATS_BEFORE_RESET statsd atom pull
-     * in persistent file.
-     */
-    public void setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(long timestamp) {
-        Properties props = new Properties();
-        lockSnapshotDirectory();
-        try {
-            try (InputStream in = mConfigFile.openRead()) {
-                props.load(in);
-            } catch (IOException e) {
-                Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
-            }
-            props.put(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY,
-                    String.valueOf(timestamp));
-            FileOutputStream out = null;
-            try {
-                out = mConfigFile.startWrite();
-                props.store(out, "Statsd atom pull timestamps");
-                mConfigFile.finishWrite(out);
-            } catch (IOException e) {
-                mConfigFile.failWrite(out);
-                Slog.e(TAG, "Cannot save config file " + mConfigFile, e);
-            }
-        } finally {
-            unlockSnapshotDirectory();
-        }
-    }
-
-    /**
-     * Retrieves the previously saved timestamp of the last BATTERY_USAGE_STATS_BEFORE_RESET
-     * statsd atom pull.
-     */
-    public long getLastBatteryUsageStatsBeforeResetAtomPullTimestamp() {
-        Properties props = new Properties();
-        lockSnapshotDirectory();
-        try {
-            try (InputStream in = mConfigFile.openRead()) {
-                props.load(in);
-            } catch (IOException e) {
-                Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
-            }
-        } finally {
-            unlockSnapshotDirectory();
-        }
-        return Long.parseLong(
-                props.getProperty(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, "0"));
-    }
-
-    private void lockSnapshotDirectory() {
-        mFileLock.lock();
-
-        // Lock the directory from access by other JVMs
-        try {
-            mLockFile.getParentFile().mkdirs();
-            mLockFile.createNewFile();
-            mJvmLock = FileChannel.open(mLockFile.toPath(), StandardOpenOption.WRITE).lock();
-        } catch (IOException e) {
-            Log.e(TAG, "Cannot lock snapshot directory", e);
-        }
-    }
-
-    private void unlockSnapshotDirectory() {
-        try {
-            mJvmLock.close();
-        } catch (IOException e) {
-            Log.e(TAG, "Cannot unlock snapshot directory", e);
-        } finally {
-            mFileLock.unlock();
-        }
-    }
-
-    /**
-     * Creates a file name by formatting the timestamp as 19-digit zero-padded number.
-     * This ensures that sorted directory list follows the chronological order.
-     */
-    private File makeSnapshotFilename(long statsEndTimestamp) {
-        return new File(mStoreDir, String.format(Locale.ENGLISH, "%019d", statsEndTimestamp)
-                + SNAPSHOT_FILE_EXTENSION);
-    }
-
-    private void writeXmlFileLocked(BatteryUsageStats stats, File file) throws IOException {
-        try (OutputStream out = new FileOutputStream(file)) {
-            TypedXmlSerializer serializer = Xml.newBinarySerializer();
-            serializer.setOutput(out, StandardCharsets.UTF_8.name());
-            serializer.startDocument(null, true);
-            stats.writeXml(serializer);
-            serializer.endDocument();
-        }
-    }
-
-    private BatteryUsageStats readXmlFileLocked(File file)
-            throws IOException, XmlPullParserException {
-        try (InputStream in = new FileInputStream(file)) {
-            TypedXmlPullParser parser = Xml.newBinaryPullParser();
-            parser.setInput(in, StandardCharsets.UTF_8.name());
-            return BatteryUsageStats.createFromXml(parser);
-        }
-    }
-
-    private void removeOldSnapshotsLocked() {
-        // Read the directory list into a _sorted_ map.  The alphanumeric ordering
-        // corresponds to the historical order of snapshots because the file names
-        // are timestamps zero-padded to the same length.
-        long totalSize = 0;
-        TreeMap<File, Long> mFileSizes = new TreeMap<>();
-        for (File file : mStoreDir.listFiles()) {
-            final long fileSize = file.length();
-            totalSize += fileSize;
-            if (file.getName().endsWith(SNAPSHOT_FILE_EXTENSION)) {
-                mFileSizes.put(file, fileSize);
-            }
-        }
-
-        while (totalSize > mMaxStorageBytes) {
-            final Map.Entry<File, Long> entry = mFileSizes.firstEntry();
-            if (entry == null) {
-                break;
-            }
-
-            File file = entry.getKey();
-            if (!file.delete()) {
-                Slog.e(TAG, "Cannot delete battery usage stats " + file);
-            }
-            totalSize -= entry.getValue();
-            mFileSizes.remove(file);
-        }
-    }
-
-    public void removeAllSnapshots() {
-        lockSnapshotDirectory();
-        try {
-            for (File file : mStoreDir.listFiles()) {
-                if (file.getName().endsWith(SNAPSHOT_FILE_EXTENSION)) {
-                    if (!file.delete()) {
-                        Slog.e(TAG, "Cannot delete battery usage stats " + file);
-                    }
-                }
-            }
-        } finally {
-            unlockSnapshotDirectory();
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
index 5b3fe06..fbf6928 100644
--- a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
@@ -16,14 +16,8 @@
 
 package com.android.server.power.stats;
 
-import android.os.BatteryConsumer;
-
-import com.android.internal.os.MultiStateStats;
-
 class CpuAggregatedPowerStats extends PowerComponentAggregatedPowerStats {
-
-    CpuAggregatedPowerStats(MultiStateStats.States[] deviceStates,
-            MultiStateStats.States[] uidStates) {
-        super(BatteryConsumer.POWER_COMPONENT_CPU, deviceStates, uidStates);
+    CpuAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) {
+        super(config);
     }
 }
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
index 686268f..05c0a13 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -21,7 +21,13 @@
 
 import com.android.internal.os.MultiStateStats;
 import com.android.internal.os.PowerStats;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
 import java.util.Collection;
 
 /**
@@ -31,6 +37,12 @@
  * as part of the {@link PowerStats.Descriptor}.
  */
 class PowerComponentAggregatedPowerStats {
+    static final String XML_TAG_POWER_COMPONENT = "power_component";
+    static final String XML_ATTR_ID = "id";
+    private static final String XML_TAG_DEVICE_STATS = "device-stats";
+    private static final String XML_TAG_UID_STATS = "uid-stats";
+    private static final String XML_ATTR_UID = "uid";
+
     public final int powerComponentId;
     private final MultiStateStats.States[] mDeviceStateConfig;
     private final MultiStateStats.States[] mUidStateConfig;
@@ -49,22 +61,24 @@
         public MultiStateStats stats;
     }
 
-    PowerComponentAggregatedPowerStats(int powerComponentId,
-            MultiStateStats.States[] deviceStates,
-            MultiStateStats.States[] uidStates) {
-        this.powerComponentId = powerComponentId;
-        mDeviceStateConfig = deviceStates;
-        mUidStateConfig = uidStates;
+    PowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) {
+        this.powerComponentId = config.getPowerComponentId();
+        mDeviceStateConfig = config.getDeviceStateConfig();
+        mUidStateConfig = config.getUidStateConfig();
         mDeviceStates = new int[mDeviceStateConfig.length];
         mDeviceStateTimestamps = new long[mDeviceStateConfig.length];
     }
 
-    void setState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) {
+    public PowerStats.Descriptor getPowerStatsDescriptor() {
+        return mPowerStatsDescriptor;
+    }
+
+    void setState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state, long time) {
         mDeviceStates[stateId] = state;
         mDeviceStateTimestamps[stateId] = time;
 
         if (mDeviceStateConfig[stateId].isTracked()) {
-            if (mDeviceStats != null || createDeviceStats()) {
+            if (mDeviceStats != null) {
                 mDeviceStats.setState(stateId, state, time);
             }
         }
@@ -72,14 +86,14 @@
         if (mUidStateConfig[stateId].isTracked()) {
             for (int i = mUidStats.size() - 1; i >= 0; i--) {
                 PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i);
-                if (uidStats.stats != null || createUidStats(uidStats)) {
+                if (uidStats.stats != null) {
                     uidStats.stats.setState(stateId, state, time);
                 }
             }
         }
     }
 
-    void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state,
+    void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state,
             long time) {
         if (!mUidStateConfig[stateId].isTracked()) {
             return;
@@ -89,7 +103,7 @@
         uidStats.states[stateId] = state;
         uidStats.stateTimestampMs[stateId] = time;
 
-        if (uidStats.stats != null || createUidStats(uidStats)) {
+        if (uidStats.stats != null) {
             uidStats.stats.setState(stateId, state, time);
         }
     }
@@ -102,13 +116,6 @@
         mPowerStatsDescriptor = powerStats.descriptor;
 
         if (mDeviceStats == null) {
-            if (mStatsFactory == null) {
-                mStatsFactory = new MultiStateStats.Factory(
-                        mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig);
-                mUidStatsFactory = new MultiStateStats.Factory(
-                        mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig);
-            }
-
             createDeviceStats();
         }
 
@@ -183,7 +190,11 @@
 
     private boolean createDeviceStats() {
         if (mStatsFactory == null) {
-            return false;
+            if (mPowerStatsDescriptor == null) {
+                return false;
+            }
+            mStatsFactory = new MultiStateStats.Factory(
+                    mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig);
         }
 
         mDeviceStats = mStatsFactory.create();
@@ -196,7 +207,11 @@
 
     private boolean createUidStats(UidStats uidStats) {
         if (mUidStatsFactory == null) {
-            return false;
+            if (mPowerStatsDescriptor == null) {
+                return false;
+            }
+            mUidStatsFactory = new MultiStateStats.Factory(
+                    mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig);
         }
 
         uidStats.stats = mUidStatsFactory.create();
@@ -211,6 +226,74 @@
         return true;
     }
 
+    public void writeXml(TypedXmlSerializer serializer) throws IOException {
+        // No stats aggregated - can skip writing XML altogether
+        if (mPowerStatsDescriptor == null) {
+            return;
+        }
+
+        serializer.startTag(null, XML_TAG_POWER_COMPONENT);
+        serializer.attributeInt(null, XML_ATTR_ID, powerComponentId);
+        mPowerStatsDescriptor.writeXml(serializer);
+
+        if (mDeviceStats != null) {
+            serializer.startTag(null, XML_TAG_DEVICE_STATS);
+            mDeviceStats.writeXml(serializer);
+            serializer.endTag(null, XML_TAG_DEVICE_STATS);
+        }
+
+        for (int i = mUidStats.size() - 1; i >= 0; i--) {
+            int uid = mUidStats.keyAt(i);
+            UidStats uidStats = mUidStats.valueAt(i);
+            if (uidStats.stats != null) {
+                serializer.startTag(null, XML_TAG_UID_STATS);
+                serializer.attributeInt(null, XML_ATTR_UID, uid);
+                uidStats.stats.writeXml(serializer);
+                serializer.endTag(null, XML_TAG_UID_STATS);
+            }
+        }
+
+        serializer.endTag(null, XML_TAG_POWER_COMPONENT);
+        serializer.flush();
+    }
+
+    public boolean readFromXml(TypedXmlPullParser parser) throws XmlPullParserException,
+            IOException {
+        int eventType = parser.getEventType();
+        while (eventType != XmlPullParser.END_DOCUMENT) {
+            if (eventType == XmlPullParser.START_TAG) {
+                switch (parser.getName()) {
+                    case PowerStats.Descriptor.XML_TAG_DESCRIPTOR:
+                        mPowerStatsDescriptor = PowerStats.Descriptor.createFromXml(parser);
+                        if (mPowerStatsDescriptor == null) {
+                            return false;
+                        }
+                        break;
+                    case XML_TAG_DEVICE_STATS:
+                        if (mDeviceStats == null) {
+                            createDeviceStats();
+                        }
+                        if (!mDeviceStats.readFromXml(parser)) {
+                            return false;
+                        }
+                        break;
+                    case XML_TAG_UID_STATS:
+                        int uid = parser.getAttributeInt(null, XML_ATTR_UID);
+                        UidStats uidStats = getUidStats(uid);
+                        if (uidStats.stats == null) {
+                            createUidStats(uidStats);
+                        }
+                        if (!uidStats.stats.readFromXml(parser)) {
+                            return false;
+                        }
+                        break;
+                }
+            }
+            eventType = parser.next();
+        }
+        return true;
+    }
+
     void dumpDevice(IndentingPrintWriter ipw) {
         if (mDeviceStats != null) {
             ipw.println(mPowerStatsDescriptor.name);
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
index 6a1c1da..f374fb7 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
@@ -15,59 +15,26 @@
  */
 package com.android.server.power.stats;
 
-import android.annotation.IntDef;
-import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 
 import com.android.internal.os.BatteryStatsHistory;
 import com.android.internal.os.BatteryStatsHistoryIterator;
-import com.android.internal.os.MultiStateStats;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.function.Consumer;
 
-class PowerStatsAggregator {
-    public static final int STATE_POWER = 0;
-    public static final int STATE_SCREEN = 1;
-    public static final int STATE_PROCESS_STATE = 2;
-
-    @IntDef({
-            STATE_POWER,
-            STATE_SCREEN,
-            STATE_PROCESS_STATE,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface TrackedState {
-    }
-
-    static final int POWER_STATE_BATTERY = 0;
-    static final int POWER_STATE_OTHER = 1;   // Plugged in, or on wireless charger, etc.
-    static final String[] STATE_LABELS_POWER = {"pwr-battery", "pwr-other"};
-
-    static final int SCREEN_STATE_ON = 0;
-    static final int SCREEN_STATE_OTHER = 1;  // Off, doze etc
-    static final String[] STATE_LABELS_SCREEN = {"scr-on", "scr-other"};
-
-    static final String[] STATE_LABELS_PROCESS_STATE;
-
-    static {
-        String[] procStateLabels = new String[BatteryConsumer.PROCESS_STATE_COUNT];
-        for (int i = 0; i < BatteryConsumer.PROCESS_STATE_COUNT; i++) {
-            procStateLabels[i] = BatteryConsumer.processStateToString(i);
-        }
-        STATE_LABELS_PROCESS_STATE = procStateLabels;
-    }
-
-    private final BatteryStatsHistory mHistory;
+/**
+ * Power stats aggregator. It reads through portions of battery stats history, finds
+ * relevant items (state changes, power stats etc) and produces one or more
+ * {@link AggregatedPowerStats} that adds up power stats from the samples found in battery history.
+ */
+public class PowerStatsAggregator {
     private final AggregatedPowerStats mStats;
+    private final BatteryStatsHistory mHistory;
 
-    private PowerStatsAggregator(BatteryStatsHistory history,
-            AggregatedPowerStats aggregatedPowerStats) {
+    public PowerStatsAggregator(AggregatedPowerStatsConfig aggregatedPowerStatsConfig,
+            BatteryStatsHistory history) {
+        mStats = new AggregatedPowerStats(aggregatedPowerStatsConfig);
         mHistory = history;
-        mStats = aggregatedPowerStats;
     }
 
     /**
@@ -82,12 +49,10 @@
      * Note: the AggregatedPowerStats object is reused, so the consumer should fully consume
      * the stats in the <code>accept</code> method and never cache it.
      */
-    void aggregateBatteryStats(long startTimeMs, long endTimeMs,
+    public void aggregatePowerStats(long startTimeMs, long endTimeMs,
             Consumer<AggregatedPowerStats> consumer) {
-        mStats.reset();
-
-        int currentBatteryState = POWER_STATE_BATTERY;
-        int currentScreenState = SCREEN_STATE_OTHER;
+        int currentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
+        int currentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
         long baseTime = -1;
         long lastTime = 0;
         try (BatteryStatsHistoryIterator iterator =
@@ -96,131 +61,60 @@
                 BatteryStats.HistoryItem item = iterator.next();
 
                 if (baseTime < 0) {
-                    mStats.setStartTime(item.currentTime);
+                    mStats.addClockUpdate(item.time, item.currentTime);
                     baseTime = item.time;
+                } else if (item.cmd == BatteryStats.HistoryItem.CMD_CURRENT_TIME
+                           || item.cmd == BatteryStats.HistoryItem.CMD_RESET) {
+                    mStats.addClockUpdate(item.time, item.currentTime);
                 }
 
                 lastTime = item.time;
 
                 int batteryState =
                         (item.states & BatteryStats.HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0
-                                ? POWER_STATE_OTHER : POWER_STATE_BATTERY;
+                                ? AggregatedPowerStatsConfig.POWER_STATE_OTHER
+                                : AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
                 if (batteryState != currentBatteryState) {
-                    mStats.setDeviceState(STATE_POWER, batteryState, item.time);
+                    mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_POWER, batteryState,
+                            item.time);
                     currentBatteryState = batteryState;
                 }
 
                 int screenState =
                         (item.states & BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG) != 0
-                                ? SCREEN_STATE_ON : SCREEN_STATE_OTHER;
+                                ? AggregatedPowerStatsConfig.SCREEN_STATE_ON
+                                : AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
                 if (screenState != currentScreenState) {
-                    mStats.setDeviceState(STATE_SCREEN, screenState, item.time);
+                    mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN, screenState,
+                            item.time);
                     currentScreenState = screenState;
                 }
 
                 if (item.processStateChange != null) {
-                    mStats.setUidState(item.processStateChange.uid, STATE_PROCESS_STATE,
+                    mStats.setUidState(item.processStateChange.uid,
+                            AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
                             item.processStateChange.processState, item.time);
                 }
 
                 if (item.powerStats != null) {
                     if (!mStats.isCompatible(item.powerStats)) {
-                        mStats.setDuration(lastTime - baseTime);
-                        consumer.accept(mStats);
+                        if (lastTime > baseTime) {
+                            mStats.setDuration(lastTime - baseTime);
+                            consumer.accept(mStats);
+                        }
                         mStats.reset();
-                        mStats.setStartTime(item.currentTime);
+                        mStats.addClockUpdate(item.time, item.currentTime);
                         baseTime = lastTime = item.time;
                     }
                     mStats.addPowerStats(item.powerStats, item.time);
                 }
             }
         }
-        mStats.setDuration(lastTime - baseTime);
-        consumer.accept(mStats);
-    }
-
-    static class Builder {
-        static class PowerComponentAggregateStatsBuilder {
-            private final int mPowerComponentId;
-            private @TrackedState int[] mTrackedDeviceStates;
-            private @TrackedState int[] mTrackedUidStates;
-
-            PowerComponentAggregateStatsBuilder(int powerComponentId) {
-                this.mPowerComponentId = powerComponentId;
-            }
-
-            public PowerComponentAggregateStatsBuilder trackDeviceStates(
-                    @TrackedState int... states) {
-                mTrackedDeviceStates = states;
-                return this;
-            }
-
-            public PowerComponentAggregateStatsBuilder trackUidStates(@TrackedState int... states) {
-                mTrackedUidStates = states;
-                return this;
-            }
-
-            private PowerComponentAggregatedPowerStats build() {
-                MultiStateStats.States[] deviceStates = new MultiStateStats.States[]{
-                        new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_POWER),
-                                PowerStatsAggregator.STATE_LABELS_POWER),
-                        new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_SCREEN),
-                                PowerStatsAggregator.STATE_LABELS_SCREEN),
-                };
-
-                MultiStateStats.States[] uidStates = new MultiStateStats.States[]{
-                        new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_POWER),
-                                PowerStatsAggregator.STATE_LABELS_POWER),
-                        new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_SCREEN),
-                                PowerStatsAggregator.STATE_LABELS_SCREEN),
-                        new MultiStateStats.States(
-                                isTracked(mTrackedUidStates, STATE_PROCESS_STATE),
-                                PowerStatsAggregator.STATE_LABELS_PROCESS_STATE),
-                };
-
-                switch (mPowerComponentId) {
-                    case BatteryConsumer.POWER_COMPONENT_CPU:
-                        return new CpuAggregatedPowerStats(deviceStates, uidStates);
-                    default:
-                        return new PowerComponentAggregatedPowerStats(mPowerComponentId,
-                                deviceStates, uidStates);
-                }
-            }
-
-            private boolean isTracked(int[] trackedStates, int state) {
-                if (trackedStates == null) {
-                    return false;
-                }
-
-                for (int trackedState : trackedStates) {
-                    if (trackedState == state) {
-                        return true;
-                    }
-                }
-                return false;
-            }
+        if (lastTime > baseTime) {
+            mStats.setDuration(lastTime - baseTime);
+            consumer.accept(mStats);
         }
 
-        private final BatteryStatsHistory mHistory;
-        private final List<PowerComponentAggregateStatsBuilder> mPowerComponents =
-                new ArrayList<>();
-
-        Builder(BatteryStatsHistory history) {
-            mHistory = history;
-        }
-
-        PowerComponentAggregateStatsBuilder trackPowerComponent(int powerComponentId) {
-            PowerComponentAggregateStatsBuilder builder = new PowerComponentAggregateStatsBuilder(
-                    powerComponentId);
-            mPowerComponents.add(builder);
-            return builder;
-        }
-
-        PowerStatsAggregator build() {
-            return new PowerStatsAggregator(mHistory, new AggregatedPowerStats(
-                    mPowerComponents.stream()
-                            .map(PowerComponentAggregateStatsBuilder::build)
-                            .toArray(PowerComponentAggregatedPowerStats[]::new)));
-        }
+        mStats.reset();     // to free up memory
     }
 }
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
new file mode 100644
index 0000000..58619c7
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.annotation.DurationMillisLong;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
+import com.android.internal.os.MonotonicClock;
+
+import java.io.PrintWriter;
+import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Controls the frequency at which {@link PowerStatsSpan}'s are generated and stored in
+ * {@link PowerStatsStore}.
+ */
+public class PowerStatsScheduler {
+    private static final long MINUTE_IN_MILLIS = TimeUnit.MINUTES.toMillis(1);
+    private static final long HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1);
+
+    private final Context mContext;
+    private boolean mEnablePeriodicPowerStatsCollection;
+    @DurationMillisLong
+    private final long mAggregatedPowerStatsSpanDuration;
+    @DurationMillisLong
+    private final long mPowerStatsAggregationPeriod;
+    private final PowerStatsStore mPowerStatsStore;
+    private final Clock mClock;
+    private final MonotonicClock mMonotonicClock;
+    private final Handler mHandler;
+    private final BatteryStatsImpl mBatteryStats;
+    private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+    private final PowerStatsAggregator mPowerStatsAggregator;
+    private long mLastSavedSpanEndMonotonicTime;
+
+    public PowerStatsScheduler(Context context, PowerStatsAggregator powerStatsAggregator,
+            @DurationMillisLong long aggregatedPowerStatsSpanDuration,
+            @DurationMillisLong long powerStatsAggregationPeriod, PowerStatsStore powerStatsStore,
+            Clock clock, MonotonicClock monotonicClock, Handler handler,
+            BatteryStatsImpl batteryStats, BatteryUsageStatsProvider batteryUsageStatsProvider) {
+        mContext = context;
+        mPowerStatsAggregator = powerStatsAggregator;
+        mAggregatedPowerStatsSpanDuration = aggregatedPowerStatsSpanDuration;
+        mPowerStatsAggregationPeriod = powerStatsAggregationPeriod;
+        mPowerStatsStore = powerStatsStore;
+        mClock = clock;
+        mMonotonicClock = monotonicClock;
+        mHandler = handler;
+        mBatteryStats = batteryStats;
+        mBatteryUsageStatsProvider = batteryUsageStatsProvider;
+    }
+
+    /**
+     * Kicks off the scheduling of power stats aggregation spans.
+     */
+    public void start(boolean enablePeriodicPowerStatsCollection) {
+        mBatteryStats.setBatteryResetListener(this::storeBatteryUsageStatsOnReset);
+        mEnablePeriodicPowerStatsCollection = enablePeriodicPowerStatsCollection;
+        if (mEnablePeriodicPowerStatsCollection) {
+            scheduleNextPowerStatsAggregation();
+        }
+    }
+
+    private void scheduleNextPowerStatsAggregation() {
+        AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
+        alarmManager.set(AlarmManager.ELAPSED_REALTIME,
+                mClock.elapsedRealtime() + mPowerStatsAggregationPeriod, "PowerStats",
+                () -> {
+                    schedulePowerStatsAggregation();
+                    mHandler.post(this::scheduleNextPowerStatsAggregation);
+                }, mHandler);
+    }
+
+    /**
+     * Initiate an asynchronous process of aggregation of power stats.
+     */
+    @VisibleForTesting
+    public void schedulePowerStatsAggregation() {
+        // Catch up the power stats collectors
+        mBatteryStats.schedulePowerStatsSampleCollection();
+        mHandler.post(this::aggregateAndStorePowerStats);
+    }
+
+    private void aggregateAndStorePowerStats() {
+        long currentTimeMillis = mClock.currentTimeMillis();
+        long currentMonotonicTime = mMonotonicClock.monotonicTime();
+        long startTime = getLastSavedSpanEndMonotonicTime();
+        long endTimeMs = alignToWallClock(startTime + mAggregatedPowerStatsSpanDuration,
+                mAggregatedPowerStatsSpanDuration, currentMonotonicTime, currentTimeMillis);
+        while (endTimeMs <= currentMonotonicTime) {
+            mPowerStatsAggregator.aggregatePowerStats(startTime, endTimeMs,
+                    stats -> {
+                        storeAggregatedPowerStats(stats);
+                        mLastSavedSpanEndMonotonicTime = stats.getStartTime() + stats.getDuration();
+                    });
+
+            startTime = endTimeMs;
+            endTimeMs += mAggregatedPowerStatsSpanDuration;
+        }
+    }
+
+    /**
+     * Performs a power stats aggregation pass and then dumps all stored aggregated power stats
+     * spans followed by the remainder that has not been stored yet.
+     */
+    public void aggregateAndDumpPowerStats(PrintWriter pw) {
+        if (mHandler.getLooper().isCurrentThread()) {
+            throw new IllegalStateException("Should not be executed on the bg handler thread.");
+        }
+
+        schedulePowerStatsAggregation();
+
+        // Wait for the aggregation process to finish storing aggregated stats spans in the store.
+        awaitCompletion();
+
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+        mHandler.post(() -> {
+            mPowerStatsStore.dump(ipw);
+            // Aggregate the remainder of power stats and dump the results without storing them yet.
+            long powerStoreEndMonotonicTime = getLastSavedSpanEndMonotonicTime();
+            mPowerStatsAggregator.aggregatePowerStats(powerStoreEndMonotonicTime, 0,
+                    stats -> {
+                        // Create a PowerStatsSpan for consistency of the textual output
+                        PowerStatsSpan span = PowerStatsStore.createPowerStatsSpan(stats);
+                        if (span != null) {
+                            span.dump(ipw);
+                        }
+                    });
+        });
+
+        awaitCompletion();
+    }
+
+    /**
+     * Align the supplied time to the wall clock, for aesthetic purposes. For example, if
+     * the schedule is configured with a 15-min interval, the captured aggregated stats will
+     * be for spans XX:00-XX:15, XX:15-XX:30, XX:30-XX:45 and XX:45-XX:60. Only the current
+     * time is used for the alignment, so if the wall clock changed during an aggregation span,
+     * or if the device was off (which stops the monotonic clock), the alignment may be
+     * temporarily broken.
+     */
+    @VisibleForTesting
+    public static long alignToWallClock(long targetMonotonicTime, long interval,
+            long currentMonotonicTime, long currentTimeMillis) {
+
+        // Estimate the wall clock time for the requested targetMonotonicTime
+        long targetWallClockTime = currentTimeMillis + (targetMonotonicTime - currentMonotonicTime);
+
+        if (interval >= MINUTE_IN_MILLIS && TimeUnit.HOURS.toMillis(1) % interval == 0) {
+            // If the interval is a divisor of an hour, e.g. 10 minutes, 15 minutes, etc
+
+            // First, round up to the next whole minute
+            Calendar cal = Calendar.getInstance();
+            cal.setTimeInMillis(targetWallClockTime + MINUTE_IN_MILLIS - 1);
+            cal.set(Calendar.SECOND, 0);
+            cal.set(Calendar.MILLISECOND, 0);
+
+            // Now set the minute to a multiple of the requested interval
+            int intervalInMinutes = (int) (interval / MINUTE_IN_MILLIS);
+            cal.set(Calendar.MINUTE,
+                    ((cal.get(Calendar.MINUTE) + intervalInMinutes - 1) / intervalInMinutes)
+                            * intervalInMinutes);
+
+            long adjustment = cal.getTimeInMillis() - targetWallClockTime;
+            return targetMonotonicTime + adjustment;
+        } else if (interval >= HOUR_IN_MILLIS && TimeUnit.DAYS.toMillis(1) % interval == 0) {
+            // If the interval is a divisor of a day, e.g. 2h, 3h, etc
+
+            // First, round up to the next whole hour
+            Calendar cal = Calendar.getInstance();
+            cal.setTimeInMillis(targetWallClockTime + HOUR_IN_MILLIS - 1);
+            cal.set(Calendar.MINUTE, 0);
+            cal.set(Calendar.SECOND, 0);
+            cal.set(Calendar.MILLISECOND, 0);
+
+            // Now set the hour of day to a multiple of the requested interval
+            int intervalInHours = (int) (interval / HOUR_IN_MILLIS);
+            cal.set(Calendar.HOUR_OF_DAY,
+                    ((cal.get(Calendar.HOUR_OF_DAY) + intervalInHours - 1) / intervalInHours)
+                    * intervalInHours);
+
+            long adjustment = cal.getTimeInMillis() - targetWallClockTime;
+            return targetMonotonicTime + adjustment;
+        }
+
+        return targetMonotonicTime;
+    }
+
+    private long getLastSavedSpanEndMonotonicTime() {
+        if (mLastSavedSpanEndMonotonicTime != 0) {
+            return mLastSavedSpanEndMonotonicTime;
+        }
+
+        for (PowerStatsSpan.Metadata metadata : mPowerStatsStore.getTableOfContents()) {
+            if (metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) {
+                for (PowerStatsSpan.TimeFrame timeFrame : metadata.getTimeFrames()) {
+                    long endMonotonicTime = timeFrame.startMonotonicTime + timeFrame.duration;
+                    if (endMonotonicTime > mLastSavedSpanEndMonotonicTime) {
+                        mLastSavedSpanEndMonotonicTime = endMonotonicTime;
+                    }
+                }
+            }
+        }
+        return mLastSavedSpanEndMonotonicTime;
+    }
+
+    private void storeAggregatedPowerStats(AggregatedPowerStats stats) {
+        mPowerStatsStore.storeAggregatedPowerStats(stats);
+    }
+
+    private void storeBatteryUsageStatsOnReset(int resetReason) {
+        if (resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE) {
+            return;
+        }
+
+        final BatteryUsageStats batteryUsageStats =
+                mBatteryUsageStatsProvider.getBatteryUsageStats(
+                        new BatteryUsageStatsQuery.Builder()
+                                .setMaxStatsAgeMs(0)
+                                .includePowerModels()
+                                .includeProcessStateData()
+                                .build());
+
+        // TODO(b/188068523): BatteryUsageStats should use monotonic time for start and end
+        // Once that change is made, we will be able to use the BatteryUsageStats' monotonic
+        // start time
+        long monotonicStartTime =
+                mMonotonicClock.monotonicTime() - batteryUsageStats.getStatsDuration();
+        mHandler.post(() ->
+                mPowerStatsStore.storeBatteryUsageStats(monotonicStartTime, batteryUsageStats));
+    }
+
+    private void awaitCompletion() {
+        ConditionVariable done = new ConditionVariable();
+        mHandler.post(done::open);
+        done.block();
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
new file mode 100644
index 0000000..3b260ca
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
@@ -0,0 +1,433 @@
+/*
+ * 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.power.stats;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import com.google.android.collect.Sets;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Contains power stats of various kinds, aggregated over a time span.
+ */
+public class PowerStatsSpan {
+    private static final String TAG = "PowerStatsStore";
+
+    /**
+     * Increment VERSION when the XML format of the store changes. Also, update
+     * {@link #isCompatibleXmlFormat} to return true for all legacy versions
+     * that are compatible with the new one.
+     */
+    private static final int VERSION = 1;
+
+    private static final String XML_TAG_METADATA = "metadata";
+    private static final String XML_ATTR_ID = "id";
+    private static final String XML_ATTR_VERSION = "version";
+    private static final String XML_TAG_TIMEFRAME = "timeframe";
+    private static final String XML_ATTR_MONOTONIC = "monotonic";
+    private static final String XML_ATTR_START_TIME = "start";
+    private static final String XML_ATTR_DURATION = "duration";
+    private static final String XML_TAG_SECTION = "section";
+    private static final String XML_ATTR_SECTION_TYPE = "type";
+
+    private static final DateTimeFormatter DATE_FORMAT =
+            DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+
+    static class TimeFrame {
+        public final long startMonotonicTime;
+        @CurrentTimeMillisLong
+        public final long startTime;
+        @DurationMillisLong
+        public final long duration;
+
+        TimeFrame(long startMonotonicTime, @CurrentTimeMillisLong long startTime,
+                @DurationMillisLong long duration) {
+            this.startMonotonicTime = startMonotonicTime;
+            this.startTime = startTime;
+            this.duration = duration;
+        }
+
+        void write(TypedXmlSerializer serializer) throws IOException {
+            serializer.startTag(null, XML_TAG_TIMEFRAME);
+            serializer.attributeLong(null, XML_ATTR_START_TIME, startTime);
+            serializer.attributeLong(null, XML_ATTR_MONOTONIC, startMonotonicTime);
+            serializer.attributeLong(null, XML_ATTR_DURATION, duration);
+            serializer.endTag(null, XML_TAG_TIMEFRAME);
+        }
+
+        static TimeFrame read(TypedXmlPullParser parser) throws XmlPullParserException {
+            return new TimeFrame(
+                    parser.getAttributeLong(null, XML_ATTR_MONOTONIC),
+                    parser.getAttributeLong(null, XML_ATTR_START_TIME),
+                    parser.getAttributeLong(null, XML_ATTR_DURATION));
+        }
+
+        /**
+         * Prints the contents of this TimeFrame.
+         */
+        public void dump(IndentingPrintWriter pw) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(DATE_FORMAT.format(Instant.ofEpochMilli(startTime)))
+                    .append(" (monotonic=").append(startMonotonicTime).append(") ")
+                    .append(" duration=");
+            String durationString = TimeUtils.formatDuration(duration);
+            if (durationString.startsWith("+")) {
+                sb.append(durationString.substring(1));
+            } else {
+                sb.append(durationString);
+            }
+            pw.print(sb);
+        }
+    }
+
+    static class Metadata {
+        static final Comparator<Metadata> COMPARATOR = Comparator.comparing(Metadata::getId);
+
+        private final long mId;
+        private final List<TimeFrame> mTimeFrames = new ArrayList<>();
+        private final List<String> mSections = new ArrayList<>();
+
+        Metadata(long id) {
+            mId = id;
+        }
+
+        public long getId() {
+            return mId;
+        }
+
+        public List<TimeFrame> getTimeFrames() {
+            return mTimeFrames;
+        }
+
+        public List<String> getSections() {
+            return mSections;
+        }
+
+        void addTimeFrame(TimeFrame timeFrame) {
+            mTimeFrames.add(timeFrame);
+        }
+
+        void addSection(String sectionType) {
+            // The number of sections per span is small, so there is no need to use a Set
+            if (!mSections.contains(sectionType)) {
+                mSections.add(sectionType);
+            }
+        }
+
+        void write(TypedXmlSerializer serializer) throws IOException {
+            serializer.startTag(null, XML_TAG_METADATA);
+            serializer.attributeLong(null, XML_ATTR_ID, mId);
+            serializer.attributeInt(null, XML_ATTR_VERSION, VERSION);
+            for (TimeFrame timeFrame : mTimeFrames) {
+                timeFrame.write(serializer);
+            }
+            for (String section : mSections) {
+                serializer.startTag(null, XML_TAG_SECTION);
+                serializer.attribute(null, XML_ATTR_SECTION_TYPE, section);
+                serializer.endTag(null, XML_TAG_SECTION);
+            }
+            serializer.endTag(null, XML_TAG_METADATA);
+        }
+
+        /**
+         * Reads just the header of the XML file containing metadata.
+         * Returns null if the file does not contain a compatible &lt;metadata&gt; element.
+         */
+        @Nullable
+        public static Metadata read(TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            Metadata metadata = null;
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.END_DOCUMENT
+                   && !(eventType == XmlPullParser.END_TAG
+                        && parser.getName().equals(XML_TAG_METADATA))) {
+                if (eventType == XmlPullParser.START_TAG) {
+                    String tagName = parser.getName();
+                    if (tagName.equals(XML_TAG_METADATA)) {
+                        int version = parser.getAttributeInt(null, XML_ATTR_VERSION);
+                        if (!isCompatibleXmlFormat(version)) {
+                            Slog.e(TAG,
+                                    "Incompatible version " + version + "; expected " + VERSION);
+                            return null;
+                        }
+
+                        long id = parser.getAttributeLong(null, XML_ATTR_ID);
+                        metadata = new Metadata(id);
+                    } else if (metadata != null && tagName.equals(XML_TAG_TIMEFRAME)) {
+                        metadata.addTimeFrame(TimeFrame.read(parser));
+                    } else if (metadata != null && tagName.equals(XML_TAG_SECTION)) {
+                        metadata.addSection(parser.getAttributeValue(null, XML_ATTR_SECTION_TYPE));
+                    }
+                }
+                eventType = parser.next();
+            }
+            return metadata;
+        }
+
+        /**
+         * Prints the metadata.
+         */
+        public void dump(IndentingPrintWriter pw) {
+            dump(pw, true);
+        }
+
+        void dump(IndentingPrintWriter pw, boolean includeSections) {
+            pw.print("Span ");
+            if (mTimeFrames.size() > 0) {
+                mTimeFrames.get(0).dump(pw);
+                pw.println();
+            }
+
+            // Sometimes, when the wall clock is adjusted in the middle of a stats session,
+            // we will have more than one time frame.
+            for (int i = 1; i < mTimeFrames.size(); i++) {
+                TimeFrame timeFrame = mTimeFrames.get(i);
+                pw.print("     ");      // Aligned below "Span "
+                timeFrame.dump(pw);
+                pw.println();
+            }
+
+            if (includeSections) {
+                pw.increaseIndent();
+                for (String section : mSections) {
+                    pw.print("section", section);
+                    pw.println();
+                }
+                pw.decreaseIndent();
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringWriter sw = new StringWriter();
+            IndentingPrintWriter ipw = new IndentingPrintWriter(sw);
+            ipw.print("id", mId);
+            for (int i = 0; i < mTimeFrames.size(); i++) {
+                TimeFrame timeFrame = mTimeFrames.get(i);
+                ipw.print("timeframe=[");
+                timeFrame.dump(ipw);
+                ipw.print("] ");
+            }
+            for (String section : mSections) {
+                ipw.print("section", section);
+            }
+            ipw.flush();
+            return sw.toString().trim();
+        }
+    }
+
+    /**
+     * Contains a specific type of aggregate power stats.  The contents type is determined by
+     * the section type.
+     */
+    public abstract static class Section {
+        private final String mType;
+
+        Section(String type) {
+            mType = type;
+        }
+
+        /**
+         * Returns the section type, which determines the type of data stored in the corresponding
+         * section of {@link PowerStatsSpan}
+         */
+        public String getType() {
+            return mType;
+        }
+
+        abstract void write(TypedXmlSerializer serializer) throws IOException;
+
+        /**
+         * Prints the section type.
+         */
+        public void dump(IndentingPrintWriter ipw) {
+            ipw.println(mType);
+        }
+    }
+
+    /**
+     * A universal XML parser for {@link PowerStatsSpan.Section}'s.  It is aware of all
+     * supported section types as well as their corresponding XML formats.
+     */
+    public interface SectionReader {
+        /**
+         * Reads the contents of the section using the parser. The type of the object
+         * read and the corresponding XML format are determined by the section type.
+         */
+        Section read(String sectionType, TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException;
+    }
+
+    private final Metadata mMetadata;
+    private final List<Section> mSections = new ArrayList<>();
+
+    public PowerStatsSpan(long id) {
+        this(new Metadata(id));
+    }
+
+    private PowerStatsSpan(Metadata metadata) {
+        mMetadata = metadata;
+    }
+
+    public Metadata getMetadata() {
+        return mMetadata;
+    }
+
+    public long getId() {
+        return mMetadata.mId;
+    }
+
+    void addTimeFrame(long monotonicTime, @CurrentTimeMillisLong long wallClockTime,
+            @DurationMillisLong long duration) {
+        mMetadata.mTimeFrames.add(new TimeFrame(monotonicTime, wallClockTime, duration));
+    }
+
+    void addSection(Section section) {
+        mMetadata.addSection(section.getType());
+        mSections.add(section);
+    }
+
+    @NonNull
+    public List<Section> getSections() {
+        return mSections;
+    }
+
+    private static boolean isCompatibleXmlFormat(int version) {
+        return version == VERSION;
+    }
+
+    /**
+     * Creates an XML file containing the persistent state of the power stats span.
+     */
+    @VisibleForTesting
+    public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
+        serializer.setOutput(out, StandardCharsets.UTF_8.name());
+        serializer.startDocument(null, true);
+        mMetadata.write(serializer);
+        for (Section section : mSections) {
+            serializer.startTag(null, XML_TAG_SECTION);
+            serializer.attribute(null, XML_ATTR_SECTION_TYPE, section.mType);
+            section.write(serializer);
+            serializer.endTag(null, XML_TAG_SECTION);
+        }
+        serializer.endDocument();
+    }
+
+    @Nullable
+    static PowerStatsSpan read(InputStream in, TypedXmlPullParser parser,
+            SectionReader sectionReader, String... sectionTypes)
+            throws IOException, XmlPullParserException {
+        Set<String> neededSections = Sets.newArraySet(sectionTypes);
+        boolean selectSections = !neededSections.isEmpty();
+        parser.setInput(in, StandardCharsets.UTF_8.name());
+
+        Metadata metadata = Metadata.read(parser);
+        if (metadata == null) {
+            return null;
+        }
+
+        PowerStatsSpan span = new PowerStatsSpan(metadata);
+        boolean skipSection = false;
+        int nestingLevel = 0;
+        int eventType = parser.getEventType();
+        while (eventType != XmlPullParser.END_DOCUMENT) {
+            if (skipSection) {
+                if (eventType == XmlPullParser.END_TAG
+                        && parser.getName().equals(XML_TAG_SECTION)) {
+                    nestingLevel--;
+                    if (nestingLevel == 0) {
+                        skipSection = false;
+                    }
+                } else if (eventType == XmlPullParser.START_TAG
+                           && parser.getName().equals(XML_TAG_SECTION)) {
+                    nestingLevel++;
+                }
+            } else if (eventType == XmlPullParser.START_TAG) {
+                String tag = parser.getName();
+                if (tag.equals(XML_TAG_SECTION)) {
+                    String sectionType = parser.getAttributeValue(null, XML_ATTR_SECTION_TYPE);
+                    if (!selectSections || neededSections.contains(sectionType)) {
+                        Section section = sectionReader.read(sectionType, parser);
+                        if (section == null) {
+                            if (selectSections) {
+                                throw new XmlPullParserException(
+                                        "Unsupported PowerStatsStore section type: " + sectionType);
+                            } else {
+                                section = new Section(sectionType) {
+                                    @Override
+                                    public void dump(IndentingPrintWriter ipw) {
+                                        ipw.println("Unsupported PowerStatsStore section type: "
+                                                    + sectionType);
+                                    }
+
+                                    @Override
+                                    void write(TypedXmlSerializer serializer) {
+                                    }
+                                };
+                            }
+                        }
+                        span.addSection(section);
+                    } else {
+                        skipSection = true;
+                    }
+                } else if (tag.equals(XML_TAG_METADATA)) {
+                    Metadata.read(parser);
+                }
+            }
+            eventType = parser.next();
+        }
+        return span;
+    }
+
+    /**
+     * Prints the contents of this power stats span.
+     */
+    public void dump(IndentingPrintWriter ipw) {
+        mMetadata.dump(ipw, /* includeSections */ false);
+        for (Section section : mSections) {
+            ipw.increaseIndent();
+            ipw.println(section.mType);
+            section.dump(ipw);
+            ipw.decreaseIndent();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsStore.java b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
new file mode 100644
index 0000000..7123bcb
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
@@ -0,0 +1,371 @@
+/*
+ * 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.power.stats;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.BatteryUsageStats;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.util.AtomicFile;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A storage mechanism for aggregated power/battery stats.
+ */
+public class PowerStatsStore {
+    private static final String TAG = "PowerStatsStore";
+
+    private static final String POWER_STATS_DIR = "power-stats";
+    private static final String POWER_STATS_SPAN_FILE_EXTENSION = ".pss";
+    private static final String DIR_LOCK_FILENAME = ".lock";
+    private static final long MAX_POWER_STATS_SPAN_STORAGE_BYTES = 100 * 1024;
+
+    private final File mSystemDir;
+    private final File mStoreDir;
+    private final File mLockFile;
+    private final ReentrantLock mFileLock = new ReentrantLock();
+    private FileLock mJvmLock;
+    private final long mMaxStorageBytes;
+    private final Handler mHandler;
+    private final PowerStatsSpan.SectionReader mSectionReader;
+    private volatile List<PowerStatsSpan.Metadata> mTableOfContents;
+
+    public PowerStatsStore(@NonNull File systemDir, Handler handler,
+            AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
+        this(systemDir, MAX_POWER_STATS_SPAN_STORAGE_BYTES, handler,
+                new DefaultSectionReader(aggregatedPowerStatsConfig));
+    }
+
+    @VisibleForTesting
+    public PowerStatsStore(@NonNull File systemDir, long maxStorageBytes, Handler handler,
+            @NonNull PowerStatsSpan.SectionReader sectionReader) {
+        mSystemDir = systemDir;
+        mStoreDir = new File(systemDir, POWER_STATS_DIR);
+        mLockFile = new File(mStoreDir, DIR_LOCK_FILENAME);
+        mHandler = handler;
+        mMaxStorageBytes = maxStorageBytes;
+        mSectionReader = sectionReader;
+        mHandler.post(this::maybeClearLegacyStore);
+    }
+
+    /**
+     * Returns the metadata for all {@link PowerStatsSpan}'s contained in the store.
+     */
+    @NonNull
+    public List<PowerStatsSpan.Metadata> getTableOfContents() {
+        List<PowerStatsSpan.Metadata> toc = mTableOfContents;
+        if (toc != null) {
+            return toc;
+        }
+
+        TypedXmlPullParser parser = Xml.newBinaryPullParser();
+        lockStoreDirectory();
+        try {
+            toc = new ArrayList<>();
+            for (File file : mStoreDir.listFiles()) {
+                String fileName = file.getName();
+                if (!fileName.endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) {
+                    continue;
+                }
+                try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
+                    parser.setInput(inputStream, StandardCharsets.UTF_8.name());
+                    PowerStatsSpan.Metadata metadata = PowerStatsSpan.Metadata.read(parser);
+                    if (metadata != null) {
+                        toc.add(metadata);
+                    } else {
+                        Slog.e(TAG, "Removing incompatible PowerStatsSpan file: " + fileName);
+                        file.delete();
+                    }
+                } catch (IOException | XmlPullParserException e) {
+                    Slog.wtf(TAG, "Cannot read PowerStatsSpan file: " + fileName);
+                }
+            }
+            toc.sort(PowerStatsSpan.Metadata.COMPARATOR);
+            mTableOfContents = Collections.unmodifiableList(toc);
+        } finally {
+            unlockStoreDirectory();
+        }
+
+        return toc;
+    }
+
+    /**
+     * Saves the specified span in the store.
+     */
+    public void storePowerStatsSpan(PowerStatsSpan span) {
+        maybeClearLegacyStore();
+        lockStoreDirectory();
+        try {
+            if (!mStoreDir.exists()) {
+                if (!mStoreDir.mkdirs()) {
+                    Slog.e(TAG, "Could not create a directory for power stats store");
+                    return;
+                }
+            }
+
+            AtomicFile file = new AtomicFile(makePowerStatsSpanFilename(span.getId()));
+            file.write(out-> {
+                try {
+                    span.writeXml(out, Xml.newBinarySerializer());
+                } catch (Exception e) {
+                    // AtomicFile will log the exception and delete the file.
+                    throw new RuntimeException(e);
+                }
+            });
+            mTableOfContents = null;
+            removeOldSpansLocked();
+        } finally {
+            unlockStoreDirectory();
+        }
+    }
+
+    /**
+     * Loads the PowerStatsSpan identified by its ID. Only loads the sections with
+     * the specified types.  Loads all sections if no sectionTypes is empty.
+     */
+    @Nullable
+    public PowerStatsSpan loadPowerStatsSpan(long id, String... sectionTypes) {
+        TypedXmlPullParser parser = Xml.newBinaryPullParser();
+        lockStoreDirectory();
+        try {
+            File file = makePowerStatsSpanFilename(id);
+            try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
+                return PowerStatsSpan.read(inputStream, parser, mSectionReader, sectionTypes);
+            } catch (IOException | XmlPullParserException e) {
+                Slog.wtf(TAG, "Cannot read PowerStatsSpan file: " + file);
+            }
+        } finally {
+            unlockStoreDirectory();
+        }
+        return null;
+    }
+
+    void storeAggregatedPowerStats(AggregatedPowerStats stats) {
+        PowerStatsSpan span = createPowerStatsSpan(stats);
+        if (span == null) {
+            return;
+        }
+        storePowerStatsSpan(span);
+    }
+
+    static PowerStatsSpan createPowerStatsSpan(AggregatedPowerStats stats) {
+        List<AggregatedPowerStats.ClockUpdate> clockUpdates = stats.getClockUpdates();
+        if (clockUpdates.isEmpty()) {
+            Slog.w(TAG, "No clock updates in aggregated power stats " + stats);
+            return null;
+        }
+
+        long monotonicTime = clockUpdates.get(0).monotonicTime;
+        long durationSum = 0;
+        PowerStatsSpan span = new PowerStatsSpan(monotonicTime);
+        for (int i = 0; i < clockUpdates.size(); i++) {
+            AggregatedPowerStats.ClockUpdate clockUpdate = clockUpdates.get(i);
+            long duration;
+            if (i == clockUpdates.size() - 1) {
+                duration = stats.getDuration() - durationSum;
+            } else {
+                duration = clockUpdate.monotonicTime - monotonicTime;
+            }
+            span.addTimeFrame(clockUpdate.monotonicTime, clockUpdate.currentTime, duration);
+            monotonicTime = clockUpdate.monotonicTime;
+            durationSum += duration;
+        }
+
+        span.addSection(new AggregatedPowerStatsSection(stats));
+        return span;
+    }
+
+    /**
+     * Stores a {@link PowerStatsSpan} containing a single section for the supplied
+     * battery usage stats.
+     */
+    public void storeBatteryUsageStats(long monotonicStartTime,
+            BatteryUsageStats batteryUsageStats) {
+        PowerStatsSpan span = new PowerStatsSpan(monotonicStartTime);
+        span.addTimeFrame(monotonicStartTime, batteryUsageStats.getStatsStartTimestamp(),
+                batteryUsageStats.getStatsDuration());
+        span.addSection(new BatteryUsageStatsSection(batteryUsageStats));
+        storePowerStatsSpan(span);
+    }
+
+    /**
+     * Creates a file name by formatting the span ID as a 19-digit zero-padded number.
+     * This ensures that the lexicographically sorted directory follows the chronological order.
+     */
+    private File makePowerStatsSpanFilename(long id) {
+        return new File(mStoreDir, String.format(Locale.ENGLISH, "%019d", id)
+                                   + POWER_STATS_SPAN_FILE_EXTENSION);
+    }
+
+    private void maybeClearLegacyStore() {
+        File legacyStoreDir = new File(mSystemDir, "battery-usage-stats");
+        if (legacyStoreDir.exists()) {
+            FileUtils.deleteContentsAndDir(legacyStoreDir);
+        }
+    }
+
+    private void lockStoreDirectory() {
+        mFileLock.lock();
+
+        // Lock the directory from access by other JVMs
+        try {
+            mLockFile.getParentFile().mkdirs();
+            mLockFile.createNewFile();
+            mJvmLock = FileChannel.open(mLockFile.toPath(), StandardOpenOption.WRITE).lock();
+        } catch (IOException e) {
+            Slog.e(TAG, "Cannot lock snapshot directory", e);
+        }
+    }
+
+    private void unlockStoreDirectory() {
+        try {
+            mJvmLock.close();
+        } catch (IOException e) {
+            Slog.e(TAG, "Cannot unlock snapshot directory", e);
+        } finally {
+            mFileLock.unlock();
+        }
+    }
+
+    private void removeOldSpansLocked() {
+        // Read the directory list into a _sorted_ map.  The alphanumeric ordering
+        // corresponds to the historical order of snapshots because the file names
+        // are timestamps zero-padded to the same length.
+        long totalSize = 0;
+        TreeMap<File, Long> mFileSizes = new TreeMap<>();
+        for (File file : mStoreDir.listFiles()) {
+            final long fileSize = file.length();
+            totalSize += fileSize;
+            if (file.getName().endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) {
+                mFileSizes.put(file, fileSize);
+            }
+        }
+
+        while (totalSize > mMaxStorageBytes) {
+            final Map.Entry<File, Long> entry = mFileSizes.firstEntry();
+            if (entry == null) {
+                break;
+            }
+
+            File file = entry.getKey();
+            if (!file.delete()) {
+                Slog.e(TAG, "Cannot delete power stats span " + file);
+            }
+            totalSize -= entry.getValue();
+            mFileSizes.remove(file);
+            mTableOfContents = null;
+        }
+    }
+
+    /**
+     * Deletes all contents from the store.
+     */
+    public void reset() {
+        lockStoreDirectory();
+        try {
+            for (File file : mStoreDir.listFiles()) {
+                if (file.getName().endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) {
+                    if (!file.delete()) {
+                        Slog.e(TAG, "Cannot delete power stats span " + file);
+                    }
+                }
+            }
+            mTableOfContents = List.of();
+        } finally {
+            unlockStoreDirectory();
+        }
+    }
+
+    /**
+     * Prints the summary of contents of the store: only metadata, but not the actual stored
+     * objects.
+     */
+    public void dumpTableOfContents(IndentingPrintWriter ipw) {
+        ipw.println("Power stats store TOC");
+        ipw.increaseIndent();
+        List<PowerStatsSpan.Metadata> contents = getTableOfContents();
+        for (PowerStatsSpan.Metadata metadata : contents) {
+            metadata.dump(ipw);
+        }
+        ipw.decreaseIndent();
+    }
+
+    /**
+     * Prints the contents of the store.
+     */
+    public void dump(IndentingPrintWriter ipw) {
+        ipw.println("Power stats store");
+        ipw.increaseIndent();
+        List<PowerStatsSpan.Metadata> contents = getTableOfContents();
+        for (PowerStatsSpan.Metadata metadata : contents) {
+            PowerStatsSpan span = loadPowerStatsSpan(metadata.getId());
+            if (span != null) {
+                span.dump(ipw);
+            }
+        }
+        ipw.decreaseIndent();
+    }
+
+    private static class DefaultSectionReader implements PowerStatsSpan.SectionReader {
+        private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+
+        DefaultSectionReader(AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
+            mAggregatedPowerStatsConfig = aggregatedPowerStatsConfig;
+        }
+
+        @Override
+        public PowerStatsSpan.Section read(String sectionType, TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            switch (sectionType) {
+                case AggregatedPowerStatsSection.TYPE:
+                    return new AggregatedPowerStatsSection(
+                            AggregatedPowerStats.createFromXml(parser,
+                                    mAggregatedPowerStatsConfig));
+                case BatteryUsageStatsSection.TYPE:
+                    return new BatteryUsageStatsSection(
+                            BatteryUsageStats.createFromXml(parser));
+                default:
+                    return null;
+            }
+        }
+    }
+}
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/security/KeyAttestationApplicationIdProviderService.java b/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java
index c908acd..d5bc912 100644
--- a/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java
+++ b/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java
@@ -24,9 +24,10 @@
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.security.keymaster.IKeyAttestationApplicationIdProvider;
-import android.security.keymaster.KeyAttestationApplicationId;
-import android.security.keymaster.KeyAttestationPackageInfo;
+import android.security.keystore.IKeyAttestationApplicationIdProvider;
+import android.security.keystore.KeyAttestationApplicationId;
+import android.security.keystore.KeyAttestationPackageInfo;
+import android.security.keystore.Signature;
 
 /**
  * @hide
@@ -64,14 +65,25 @@
             for (int i = 0; i < packageNames.length; ++i) {
                 PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(packageNames[i],
                         PackageManager.GET_SIGNATURES, userId);
-                keyAttestationPackageInfos[i] = new KeyAttestationPackageInfo(packageNames[i],
-                        packageInfo.getLongVersionCode(), packageInfo.signatures);
+                KeyAttestationPackageInfo pInfo = new KeyAttestationPackageInfo();
+                pInfo.packageName = new String(packageNames[i]);
+                pInfo.versionCode = packageInfo.getLongVersionCode();
+                pInfo.signatures = new Signature[packageInfo.signatures.length];
+                for (int index = 0; index < packageInfo.signatures.length; index++) {
+                    Signature sign = new Signature();
+                    sign.data = packageInfo.signatures[index].toByteArray();
+                    pInfo.signatures[index] = sign;
+                }
+
+                keyAttestationPackageInfos[i] = pInfo;
             }
         } catch (NameNotFoundException nnfe) {
             throw new RemoteException(nnfe.getMessage());
         } finally {
             Binder.restoreCallingIdentity(token);
         }
-        return new KeyAttestationApplicationId(keyAttestationPackageInfos);
+        KeyAttestationApplicationId attestAppId = new KeyAttestationApplicationId();
+        attestAppId.packageInfos = keyAttestationPackageInfos;
+        return attestAppId;
     }
 }
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/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
index bff6d50..6d580e9 100644
--- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
@@ -39,8 +39,10 @@
 import android.speech.RecognitionService;
 import android.speech.SpeechRecognizer;
 import android.util.Slog;
+import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.modules.expresslog.Counter;
 import com.android.server.infra.AbstractPerUserSystemService;
 
 import java.util.HashMap;
@@ -64,6 +66,9 @@
     private final Map<Integer, Set<RemoteSpeechRecognitionService>> mRemoteServicesByUid =
             new HashMap<>();
 
+    @GuardedBy("mLock")
+    private final SparseIntArray mSessionCountByUid = new SparseIntArray();
+
     SpeechRecognitionManagerServiceImpl(
             @NonNull SpeechRecognitionManagerService master,
             @NonNull Object lock, @UserIdInt int userId) {
@@ -216,6 +221,7 @@
             service.shutdown(clientToken);
         }
         synchronized (mLock) {
+            decrementSessionCountForUidLocked(callingUid);
             if (!service.hasActiveSessions()) {
                 removeService(callingUid, service);
             }
@@ -239,6 +245,26 @@
         return ComponentName.unflattenFromString(serviceName);
     }
 
+    @GuardedBy("mLock")
+    private int getSessionCountByUidLocked(int uid) {
+        return mSessionCountByUid.get(uid, 0);
+    }
+
+    @GuardedBy("mLock")
+    private void incrementSessionCountForUidLocked(int uid) {
+        mSessionCountByUid.put(uid, mSessionCountByUid.get(uid, 0) + 1);
+    }
+
+    @GuardedBy("mLock")
+    private void decrementSessionCountForUidLocked(int uid) {
+        int newCount = mSessionCountByUid.get(uid, 1) - 1;
+        if (newCount > 0) {
+            mSessionCountByUid.put(uid, newCount);
+        } else {
+            mSessionCountByUid.delete(uid);
+        }
+    }
+
     private RemoteSpeechRecognitionService createService(
             int callingUid, ComponentName serviceComponent) {
         synchronized (mLock) {
@@ -247,6 +273,18 @@
 
             if (servicesForClient != null
                     && servicesForClient.size() >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) {
+                Slog.w(TAG, "Number of remote services exceeded for uid: " + callingUid);
+                Counter.logIncrementWithUid(
+                        "speech_recognition.value_exceed_service_connections_count",
+                        callingUid);
+                return null;
+            }
+
+            if (getSessionCountByUidLocked(callingUid) >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) {
+                Slog.w(TAG, "Number of sessions exceeded for uid: " + callingUid);
+                Counter.logIncrementWithUid(
+                        "speech_recognition.value_exceed_session_count",
+                        callingUid);
                 return null;
             }
 
@@ -262,6 +300,7 @@
                         Slog.i(TAG, "Reused existing connection to " + serviceComponent);
                     }
 
+                    incrementSessionCountForUidLocked(callingUid);
                     return existingService.get();
                 }
             }
@@ -282,6 +321,7 @@
                 Slog.i(TAG, "Creating a new connection to " + serviceComponent);
             }
 
+            incrementSessionCountForUidLocked(callingUid);
             return service;
         }
     }
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 ed10346..a01113b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1316,6 +1316,9 @@
         if (mLaunchIntoPipHostActivity != null) {
             pw.println(prefix + "launchIntoPipHostActivity=" + mLaunchIntoPipHostActivity);
         }
+        if (mWaitForEnteringPinnedMode) {
+            pw.print(prefix); pw.println("mWaitForEnteringPinnedMode=true");
+        }
 
         mLetterboxUiController.dump(pw, prefix);
 
@@ -3132,9 +3135,7 @@
     }
 
     boolean canReceiveKeys() {
-        // TODO(156521483): Propagate the state down the hierarchy instead of checking the parent
-        return getWindowConfiguration().canReceiveKeys()
-                && (task == null || task.getWindowConfiguration().canReceiveKeys());
+        return getWindowConfiguration().canReceiveKeys() && !mWaitForEnteringPinnedMode;
     }
 
     boolean isResizeable() {
@@ -5367,18 +5368,11 @@
                 // rather than just direct membership.
                 inFinishingTransition = mTransitionController.inFinishingTransition(this);
                 if (!inFinishingTransition && (visible || !mDisplayContent.isSleeping())) {
-                    Slog.e(TAG, "setVisibility=" + visible
-                            + " while transition is not collecting or finishing "
-                            + this + " caller=" + Debug.getCallers(8));
-                    // Force showing the parents because they may be hidden by previous transition.
                     if (visible) {
-                        final Transaction t = getSyncTransaction();
-                        for (WindowContainer<?> p = getParent(); p != null && p != mDisplayContent;
-                                p = p.getParent()) {
-                            if (p.mSurfaceControl != null) {
-                                t.show(p.mSurfaceControl);
-                            }
-                        }
+                        mTransitionController.onVisibleWithoutCollectingTransition(this,
+                                Debug.getCallers(1, 1));
+                    } else {
+                        Slog.w(TAG, "Set invisible without transition " + this);
                     }
                 }
             }
@@ -8271,7 +8265,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();
@@ -8279,11 +8277,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();
@@ -9269,13 +9263,6 @@
             Slog.w(TAG, errorMessage);
         }
 
-        // Configuration's equality doesn't consider seq so if only seq number changes in resolved
-        // override configuration. Therefore ConfigurationContainer doesn't change merged override
-        // configuration, but it's used to push configuration changes so explicitly update that.
-        if (getMergedOverrideConfiguration().seq != getResolvedOverrideConfiguration().seq) {
-            onMergedOverrideConfigurationChanged();
-        }
-
         // Before PiP animation is done, th windowing mode of the activity is still the previous
         // mode (see RootWindowContainer#moveActivityToPinnedRootTask). So once the windowing mode
         // of activity is changed, it is the signal of the last step to update the PiP states.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 27315bb..7cccf6b 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -707,7 +707,7 @@
                 }
             }
 
-            int res;
+            int res = START_CANCELED;
             synchronized (mService.mGlobalLock) {
                 final boolean globalConfigWillChange = mRequest.globalConfig != null
                         && mService.getGlobalConfiguration().diff(mRequest.globalConfig) != 0;
@@ -719,22 +719,20 @@
                         + "will change = %b", globalConfigWillChange);
 
                 final long origId = Binder.clearCallingIdentity();
-
-                res = resolveToHeavyWeightSwitcherIfNeeded();
-                if (res != START_SUCCESS) {
-                    return res;
-                }
-
                 try {
+                    res = resolveToHeavyWeightSwitcherIfNeeded();
+                    if (res != START_SUCCESS) {
+                        return res;
+                    }
+
                     res = executeRequest(mRequest);
                 } finally {
+                    Binder.restoreCallingIdentity(origId);
                     mRequest.logMessage.append(" result code=").append(res);
                     Slog.i(TAG, mRequest.logMessage.toString());
                     mRequest.logMessage.setLength(0);
                 }
 
-                Binder.restoreCallingIdentity(origId);
-
                 if (globalConfigWillChange) {
                     // If the caller also wants to switch to a new configuration, do so now.
                     // This allows a clean switch, as we are waiting for the current activity
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 9fd4720..12e1e2c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -47,6 +47,7 @@
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
@@ -1592,6 +1593,9 @@
     }
 
     private void removePinnedRootTaskInSurfaceTransaction(Task rootTask) {
+        rootTask.mTransitionController.requestTransitionIfNeeded(TRANSIT_TO_BACK, 0 /* flags */,
+                rootTask, rootTask.mDisplayContent, null /* remoteTransition */,
+                null /* displayChange */);
         /**
          * Workaround: Force-stop all the activities in the root pinned task before we reparent them
          * to the fullscreen root task.  This is to guarantee that when we are removing a root task,
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index b039646..3dc377d 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -22,6 +22,7 @@
 import android.view.SurfaceControl.Transaction;
 import android.view.animation.Animation;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
@@ -31,7 +32,8 @@
  * Interface that describes an animation and bridges the animation start to the component
  * responsible for running the animation.
  */
-interface AnimationAdapter {
+@VisibleForTesting
+public interface AnimationAdapter {
 
     long STATUS_BAR_TRANSITION_DURATION = 120L;
 
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 58d4e82..7947112 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -223,9 +223,9 @@
     }
 
     /**
-     * Update merged override configuration based on corresponding parent's config and notify all
-     * its children. If there is no parent, merged override configuration will set equal to current
-     * override config.
+     * Update merged override configuration based on corresponding parent's config. If there is no
+     * parent, merged override configuration will set equal to current override config. This
+     * doesn't cascade on its own since it's called by {@link #onConfigurationChanged}.
      * @see #mMergedOverrideConfiguration
      */
     void onMergedOverrideConfigurationChanged() {
@@ -240,10 +240,6 @@
         } else {
             mMergedOverrideConfiguration.setTo(mResolvedOverrideConfiguration);
         }
-        for (int i = getChildCount() - 1; i >= 0; --i) {
-            final ConfigurationContainer child = getChildAt(i);
-            child.onMergedOverrideConfigurationChanged();
-        }
     }
 
     /**
@@ -688,8 +684,6 @@
         if (newParent != null) {
             // Update full configuration of this container and all its children.
             onConfigurationChanged(newParent.mFullConfiguration);
-            // Update merged override configuration of this container and all its children.
-            onMergedOverrideConfigurationChanged();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index ae29afa..64a230e 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -11,172 +11,36 @@
  * 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
+ * limitations under the License.
  */
 
 package com.android.server.wm;
 
-import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
-import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
-import static com.android.server.wm.AlphaAnimationSpecProto.TO;
-import static com.android.server.wm.AnimationSpecProto.ALPHA;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
-
 import android.annotation.NonNull;
 import android.graphics.Rect;
-import android.util.Log;
-import android.util.proto.ProtoOutputStream;
-import android.view.Surface;
 import android.view.SurfaceControl;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
-
-import java.io.PrintWriter;
+import com.android.window.flags.Flags;
 
 /**
  * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is
  * black layers of varying opacity at various Z-levels which create the effect of a Dim.
  */
-class Dimmer {
-    private static final String TAG = "WindowManager";
-    // This is in milliseconds.
-    private static final int DEFAULT_DIM_ANIM_DURATION = 200;
-
-    private class DimAnimatable implements SurfaceAnimator.Animatable {
-        private SurfaceControl mDimLayer;
-
-        private DimAnimatable(SurfaceControl dimLayer) {
-            mDimLayer = dimLayer;
-        }
-
-        @Override
-        public SurfaceControl.Transaction getSyncTransaction() {
-            return mHost.getSyncTransaction();
-        }
-
-        @Override
-        public SurfaceControl.Transaction getPendingTransaction() {
-            return mHost.getPendingTransaction();
-        }
-
-        @Override
-        public void commitPendingTransaction() {
-            mHost.commitPendingTransaction();
-        }
-
-        @Override
-        public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
-        }
-
-        @Override
-        public void onAnimationLeashLost(SurfaceControl.Transaction t) {
-        }
-
-        @Override
-        public SurfaceControl.Builder makeAnimationLeash() {
-            return mHost.makeAnimationLeash();
-        }
-
-        @Override
-        public SurfaceControl getAnimationLeashParent() {
-            return mHost.getSurfaceControl();
-        }
-
-        @Override
-        public SurfaceControl getSurfaceControl() {
-            return mDimLayer;
-        }
-
-        @Override
-        public SurfaceControl getParentSurfaceControl() {
-            return mHost.getSurfaceControl();
-        }
-
-        @Override
-        public int getSurfaceWidth() {
-            // This will determine the size of the leash created. This should be the size of the
-            // host and not the dim layer since the dim layer may get bigger during animation. If
-            // that occurs, the leash size cannot change so we need to ensure the leash is big
-            // enough that the dim layer can grow.
-            // This works because the mHost will be a Task which has the display bounds.
-            return mHost.getSurfaceWidth();
-        }
-
-        @Override
-        public int getSurfaceHeight() {
-            // See getSurfaceWidth() above for explanation.
-            return mHost.getSurfaceHeight();
-        }
-
-        void removeSurface() {
-            if (mDimLayer != null && mDimLayer.isValid()) {
-                getSyncTransaction().remove(mDimLayer);
-            }
-            mDimLayer = null;
-        }
-    }
-
-    @VisibleForTesting
-    class DimState {
-        /**
-         * The layer where property changes should be invoked on.
-         */
-        SurfaceControl mDimLayer;
-        boolean mDimming;
-        boolean isVisible;
-        SurfaceAnimator mSurfaceAnimator;
-
-        // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
-        final Rect mDimBounds = new Rect();
-
-        /**
-         * Determines whether the dim layer should animate before destroying.
-         */
-        boolean mAnimateExit = true;
-
-        /**
-         * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for
-         * details on Dim lifecycle.
-         */
-        boolean mDontReset;
-
-        DimState(SurfaceControl dimLayer) {
-            mDimLayer = dimLayer;
-            mDimming = true;
-            final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer);
-            mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> {
-                if (!mDimming) {
-                    dimAnimatable.removeSurface();
-                }
-            }, mHost.mWmService);
-        }
-    }
-
+public abstract class Dimmer {
     /**
-     * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the
+     * The {@link WindowContainer} that our Dims are bounded to. We may be dimming on behalf of the
      * host, some controller of it, or one of the hosts children.
      */
-    private WindowContainer mHost;
-    private WindowContainer mLastRequestedDimContainer;
-    @VisibleForTesting
-    DimState mDimState;
+    protected final WindowContainer mHost;
 
-    private final SurfaceAnimatorStarter mSurfaceAnimatorStarter;
-
-    @VisibleForTesting
-    interface SurfaceAnimatorStarter {
-        void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
-                AnimationAdapter anim, boolean hidden, @AnimationType int type);
-    }
-
-    Dimmer(WindowContainer host) {
-        this(host, SurfaceAnimator::startAnimation);
-    }
-
-    Dimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) {
+    protected Dimmer(WindowContainer host) {
         mHost = host;
-        mSurfaceAnimatorStarter = surfaceAnimatorStarter;
+    }
+
+    // Constructs the correct type of dimmer
+    static Dimmer create(WindowContainer host) {
+        return Flags.dimmerRefactor() ? new SmoothDimmer(host) : new LegacyDimmer(host);
     }
 
     @NonNull
@@ -184,49 +48,8 @@
         return mHost;
     }
 
-    private SurfaceControl makeDimLayer() {
-        return mHost.makeChildSurface(null)
-                .setParent(mHost.getSurfaceControl())
-                .setColorLayer()
-                .setName("Dim Layer for - " + mHost.getName())
-                .setCallsite("Dimmer.makeDimLayer")
-                .build();
-    }
-
-    /**
-     * Retrieve the DimState, creating one if it doesn't exist.
-     */
-    private DimState getDimState(WindowContainer container) {
-        if (mDimState == null) {
-            try {
-                final SurfaceControl ctl = makeDimLayer();
-                mDimState = new DimState(ctl);
-            } catch (Surface.OutOfResourcesException e) {
-                Log.w(TAG, "OutOfResourcesException creating dim surface");
-            }
-        }
-
-        mLastRequestedDimContainer = container;
-        return mDimState;
-    }
-
-    private void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) {
-        final DimState d = getDimState(container);
-
-        if (d == null) {
-            return;
-        }
-
-        // The dim method is called from WindowState.prepareSurfaces(), which is always called
-        // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
-        // relative to the highest Z layer with a dim.
-        SurfaceControl.Transaction t = mHost.getPendingTransaction();
-        t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
-        t.setAlpha(d.mDimLayer, alpha);
-        t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
-
-        d.mDimming = true;
-    }
+    protected abstract void dim(
+            WindowContainer container, int relativeLayer, float alpha, int blurRadius);
 
     /**
      * Place a dim above the given container, which should be a child of the host container.
@@ -260,25 +83,15 @@
      * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
      * a chance to request dims to continue.
      */
-    void resetDimStates() {
-        if (mDimState == null) {
-            return;
-        }
-        if (!mDimState.mDontReset) {
-            mDimState.mDimming = false;
-        }
-    }
+    abstract void resetDimStates();
 
     /** Returns non-null bounds if the dimmer is showing. */
-    Rect getDimBounds() {
-        return mDimState != null ? mDimState.mDimBounds : null;
-    }
+    abstract Rect getDimBounds();
 
-    void dontAnimateExit() {
-        if (mDimState != null) {
-            mDimState.mAnimateExit = false;
-        }
-    }
+    abstract void dontAnimateExit();
+
+    @VisibleForTesting
+    abstract SurfaceControl getDimLayer();
 
     /**
      * Call after invoking {@link WindowContainer#prepareSurfaces} on children as
@@ -288,109 +101,5 @@
      * @param t      A transaction in which to update the dims.
      * @return true if any Dims were updated.
      */
-    boolean updateDims(SurfaceControl.Transaction t) {
-        if (mDimState == null) {
-            return false;
-        }
-
-        if (!mDimState.mDimming) {
-            if (!mDimState.mAnimateExit) {
-                if (mDimState.mDimLayer.isValid()) {
-                    t.remove(mDimState.mDimLayer);
-                }
-            } else {
-                startDimExit(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t);
-            }
-            mDimState = null;
-            return false;
-        } else {
-            final Rect bounds = mDimState.mDimBounds;
-            // TODO: Once we use geometry from hierarchy this falls away.
-            t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
-            t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
-            if (!mDimState.isVisible) {
-                mDimState.isVisible = true;
-                t.show(mDimState.mDimLayer);
-                // Skip enter animation while starting window is on top of its activity
-                final WindowState ws = mLastRequestedDimContainer.asWindowState();
-                if (ws == null || ws.mActivityRecord == null
-                        || ws.mActivityRecord.mStartingData == null) {
-                    startDimEnter(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t);
-                }
-            }
-            return true;
-        }
-    }
-
-    private void startDimEnter(WindowContainer container, SurfaceAnimator animator,
-            SurfaceControl.Transaction t) {
-        startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */);
-    }
-
-    private void startDimExit(WindowContainer container, SurfaceAnimator animator,
-            SurfaceControl.Transaction t) {
-        startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */);
-    }
-
-    private void startAnim(WindowContainer container, SurfaceAnimator animator,
-            SurfaceControl.Transaction t, float startAlpha, float endAlpha) {
-        mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter(
-                new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)),
-                mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */,
-                ANIMATION_TYPE_DIMMER);
-    }
-
-    private long getDimDuration(WindowContainer container) {
-        // If there's no container, then there isn't an animation occurring while dimming. Set the
-        // duration to 0 so it immediately dims to the set alpha.
-        if (container == null) {
-            return 0;
-        }
-
-        // Otherwise use the same duration as the animation on the WindowContainer
-        AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
-        final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
-        return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
-                : animationAdapter.getDurationHint();
-    }
-
-    private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec {
-        private final long mDuration;
-        private final float mFromAlpha;
-        private final float mToAlpha;
-
-        AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) {
-            mFromAlpha = fromAlpha;
-            mToAlpha = toAlpha;
-            mDuration = duration;
-        }
-
-        @Override
-        public long getDuration() {
-            return mDuration;
-        }
-
-        @Override
-        public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
-            final float fraction = getFraction(currentPlayTime);
-            final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha;
-            t.setAlpha(sc, alpha);
-        }
-
-        @Override
-        public void dump(PrintWriter pw, String prefix) {
-            pw.print(prefix); pw.print("from="); pw.print(mFromAlpha);
-            pw.print(" to="); pw.print(mToAlpha);
-            pw.print(" duration="); pw.println(mDuration);
-        }
-
-        @Override
-        public void dumpDebugInner(ProtoOutputStream proto) {
-            final long token = proto.start(ALPHA);
-            proto.write(FROM, mFromAlpha);
-            proto.write(TO, mToAlpha);
-            proto.write(DURATION_MS, mDuration);
-            proto.end(token);
-        }
-    }
+    abstract boolean updateDims(SurfaceControl.Transaction t);
 }
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index f81e5d4..f51bf7f 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -44,6 +44,7 @@
 import android.window.DisplayAreaInfo;
 import android.window.IDisplayAreaOrganizer;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.policy.WindowManagerPolicy;
 
@@ -53,7 +54,6 @@
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
-
 /**
  * Container for grouping WindowContainer below DisplayContent.
  *
@@ -79,6 +79,12 @@
     private final Configuration mTmpConfiguration = new Configuration();
 
     /**
+     * Prevent duplicate calls to onDisplayAreaAppeared, or early call of onDisplayAreaInfoChanged.
+     */
+    @VisibleForTesting
+    boolean mDisplayAreaAppearedSent;
+
+    /**
      * Whether this {@link DisplayArea} should ignore fixed-orientation request. If {@code true}, it
      * can never specify orientation, but shows the fixed-orientation apps below it in the
      * letterbox; otherwise, it rotates based on the fixed-orientation request.
@@ -582,18 +588,31 @@
         sendDisplayAreaVanished(lastOrganizer);
         if (!skipDisplayAreaAppeared) {
             sendDisplayAreaAppeared();
+        } else if (organizer != null) {
+            // Set as sent since the DisplayAreaAppearedInfo will be sent back when registered.
+            mDisplayAreaAppearedSent = true;
         }
     }
 
+    @VisibleForTesting
     void sendDisplayAreaAppeared() {
-        if (mOrganizer == null) return;
+        if (mOrganizer == null || mDisplayAreaAppearedSent) return;
         mOrganizerController.onDisplayAreaAppeared(mOrganizer, this);
+        mDisplayAreaAppearedSent = true;
     }
 
+    @VisibleForTesting
+    void sendDisplayAreaInfoChanged() {
+        if (mOrganizer == null || !mDisplayAreaAppearedSent) return;
+        mOrganizerController.onDisplayAreaInfoChanged(mOrganizer, this);
+    }
+
+    @VisibleForTesting
     void sendDisplayAreaVanished(IDisplayAreaOrganizer organizer) {
-        if (organizer == null) return;
+        if (organizer == null || !mDisplayAreaAppearedSent) return;
         migrateToNewSurfaceControl(getSyncTransaction());
         mOrganizerController.onDisplayAreaVanished(organizer, this);
+        mDisplayAreaAppearedSent = false;
     }
 
     @Override
@@ -603,7 +622,7 @@
         super.onConfigurationChanged(newParentConfig);
 
         if (mOrganizer != null && getConfiguration().diff(mTmpConfiguration) != 0) {
-            mOrganizerController.onDisplayAreaInfoChanged(mOrganizer, this);
+            sendDisplayAreaInfoChanged();
         }
     }
 
@@ -766,7 +785,7 @@
      * DisplayArea that can be dimmed.
      */
     static class Dimmable extends DisplayArea<DisplayArea> {
-        private final Dimmer mDimmer = new Dimmer(this);
+        private final Dimmer mDimmer = Dimmer.create(this);
 
         Dimmable(WindowManagerService wms, Type type, String name, int featureId) {
             super(wms, type, name, featureId);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f6fe9b1..b7b5c2af 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -218,6 +218,7 @@
 import android.view.DisplayInfo;
 import android.view.DisplayShape;
 import android.view.Gravity;
+import android.view.IDecorViewGestureListener;
 import android.view.IDisplayWindowInsetsController;
 import android.view.ISystemGestureExclusionListener;
 import android.view.IWindow;
@@ -471,6 +472,8 @@
 
     private final RemoteCallbackList<ISystemGestureExclusionListener>
             mSystemGestureExclusionListeners = new RemoteCallbackList<>();
+    private final RemoteCallbackList<IDecorViewGestureListener> mDecorViewGestureListener =
+            new RemoteCallbackList<>();
     private final Region mSystemGestureExclusion = new Region();
     private boolean mSystemGestureExclusionWasRestricted = false;
     private final Region mSystemGestureExclusionUnrestricted = new Region();
@@ -5968,6 +5971,27 @@
         mSystemGestureExclusionListeners.unregister(listener);
     }
 
+    void registerDecorViewGestureListener(IDecorViewGestureListener listener) {
+        mDecorViewGestureListener.register(listener);
+    }
+
+    void unregisterDecorViewGestureListener(IDecorViewGestureListener listener) {
+        mDecorViewGestureListener.unregister(listener);
+    }
+
+    void updateDecorViewGestureIntercepted(IBinder token, boolean intercepted) {
+        for (int i = mDecorViewGestureListener.beginBroadcast() - 1; i >= 0; --i) {
+            try {
+                mDecorViewGestureListener
+                        .getBroadcastItem(i)
+                        .onInterceptionChanged(token, intercepted);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to notify DecorViewGestureListener", e);
+            }
+        }
+        mDecorViewGestureListener.finishBroadcast();
+    }
+
     void updateKeepClearAreas() {
         final Set<Rect> restrictedKeepClearAreas = new ArraySet<>();
         final Set<Rect> unrestrictedKeepClearAreas = new ArraySet<>();
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index c21930d..1fa7d2a 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -51,7 +51,8 @@
     private final Rect mOldWindowCrop = new Rect();
 
     InputConsumerImpl(WindowManagerService service, IBinder token, String name,
-            InputChannel inputChannel, int clientPid, UserHandle clientUser, int displayId) {
+            InputChannel inputChannel, int clientPid, UserHandle clientUser, int displayId,
+            SurfaceControl.Transaction t) {
         mService = service;
         mToken = token;
         mName = name;
@@ -82,6 +83,7 @@
                 .setName("Input Consumer " + name)
                 .setCallsite("InputConsumerImpl")
                 .build();
+        mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
     }
 
     void linkToDeathRecipient() {
@@ -129,14 +131,12 @@
 
     void show(SurfaceControl.Transaction t, WindowContainer w) {
         t.show(mInputSurface);
-        mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
         t.setRelativeLayer(mInputSurface, w.getSurfaceControl(), 1);
     }
 
     void show(SurfaceControl.Transaction t, int layer) {
         t.show(mInputSurface);
-        mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
         t.setLayer(mInputSurface, layer);
     }
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index af307ec3..5c0bc28 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -224,7 +224,7 @@
         }
 
         final InputConsumerImpl consumer = new InputConsumerImpl(mService, token, name,
-                inputChannel, clientPid, clientUser, mDisplayId);
+                inputChannel, clientPid, clientUser, mDisplayId, mInputTransaction);
         switch (name) {
             case INPUT_CONSUMER_WALLPAPER:
                 consumer.mWindowHandle.inputConfig |= InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER;
@@ -675,11 +675,6 @@
                     w.getKeyInterceptionInfo());
 
             if (w.mWinAnimator.hasSurface()) {
-                // Update trusted overlay changes here because they are tied to input info. Input
-                // changes can be updated even if surfaces aren't.
-                inputWindowHandle.setTrustedOverlay(mInputTransaction,
-                        w.mWinAnimator.mSurfaceController.mSurfaceControl,
-                        w.isWindowTrustedOverlay());
                 populateInputWindowHandle(inputWindowHandle, w);
                 setInputWindowInfoIfNeeded(mInputTransaction,
                         w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
diff --git a/services/core/java/com/android/server/wm/LegacyDimmer.java b/services/core/java/com/android/server/wm/LegacyDimmer.java
new file mode 100644
index 0000000..ccf956e
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LegacyDimmer.java
@@ -0,0 +1,341 @@
+/*
+ * 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.wm;
+
+import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
+import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
+import static com.android.server.wm.AlphaAnimationSpecProto.TO;
+import static com.android.server.wm.AnimationSpecProto.ALPHA;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.graphics.Rect;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+
+public class LegacyDimmer extends Dimmer {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
+    // This is in milliseconds.
+    private static final int DEFAULT_DIM_ANIM_DURATION = 200;
+    DimState mDimState;
+    private WindowContainer mLastRequestedDimContainer;
+    private final SurfaceAnimatorStarter mSurfaceAnimatorStarter;
+
+    private class DimAnimatable implements SurfaceAnimator.Animatable {
+        private SurfaceControl mDimLayer;
+
+        private DimAnimatable(SurfaceControl dimLayer) {
+            mDimLayer = dimLayer;
+        }
+
+        @Override
+        public SurfaceControl.Transaction getSyncTransaction() {
+            return mHost.getSyncTransaction();
+        }
+
+        @Override
+        public SurfaceControl.Transaction getPendingTransaction() {
+            return mHost.getPendingTransaction();
+        }
+
+        @Override
+        public void commitPendingTransaction() {
+            mHost.commitPendingTransaction();
+        }
+
+        @Override
+        public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
+        }
+
+        @Override
+        public void onAnimationLeashLost(SurfaceControl.Transaction t) {
+        }
+
+        @Override
+        public SurfaceControl.Builder makeAnimationLeash() {
+            return mHost.makeAnimationLeash();
+        }
+
+        @Override
+        public SurfaceControl getAnimationLeashParent() {
+            return mHost.getSurfaceControl();
+        }
+
+        @Override
+        public SurfaceControl getSurfaceControl() {
+            return mDimLayer;
+        }
+
+        @Override
+        public SurfaceControl getParentSurfaceControl() {
+            return mHost.getSurfaceControl();
+        }
+
+        @Override
+        public int getSurfaceWidth() {
+            // This will determine the size of the leash created. This should be the size of the
+            // host and not the dim layer since the dim layer may get bigger during animation. If
+            // that occurs, the leash size cannot change so we need to ensure the leash is big
+            // enough that the dim layer can grow.
+            // This works because the mHost will be a Task which has the display bounds.
+            return mHost.getSurfaceWidth();
+        }
+
+        @Override
+        public int getSurfaceHeight() {
+            // See getSurfaceWidth() above for explanation.
+            return mHost.getSurfaceHeight();
+        }
+
+        void removeSurface() {
+            if (mDimLayer != null && mDimLayer.isValid()) {
+                getSyncTransaction().remove(mDimLayer);
+            }
+            mDimLayer = null;
+        }
+    }
+
+    @VisibleForTesting
+    class DimState {
+        /**
+         * The layer where property changes should be invoked on.
+         */
+        SurfaceControl mDimLayer;
+        boolean mDimming;
+        boolean mIsVisible;
+
+        // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
+        final Rect mDimBounds = new Rect();
+
+        /**
+         * Determines whether the dim layer should animate before destroying.
+         */
+        boolean mAnimateExit = true;
+
+        /**
+         * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for
+         * details on Dim lifecycle.
+         */
+        boolean mDontReset;
+        SurfaceAnimator mSurfaceAnimator;
+
+        DimState(SurfaceControl dimLayer) {
+            mDimLayer = dimLayer;
+            mDimming = true;
+            final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer);
+            mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> {
+                if (!mDimming) {
+                    dimAnimatable.removeSurface();
+                }
+            }, mHost.mWmService);
+        }
+    }
+
+    @VisibleForTesting
+    interface SurfaceAnimatorStarter {
+        void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
+                AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type);
+    }
+
+    protected LegacyDimmer(WindowContainer host) {
+        this(host, SurfaceAnimator::startAnimation);
+    }
+
+    LegacyDimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) {
+        super(host);
+        mSurfaceAnimatorStarter = surfaceAnimatorStarter;
+    }
+
+    private DimState obtainDimState(WindowContainer container) {
+        if (mDimState == null) {
+            try {
+                final SurfaceControl ctl = makeDimLayer();
+                mDimState = new DimState(ctl);
+            } catch (Surface.OutOfResourcesException e) {
+                Log.w(TAG, "OutOfResourcesException creating dim surface");
+            }
+        }
+
+        mLastRequestedDimContainer = container;
+        return mDimState;
+    }
+
+    private SurfaceControl makeDimLayer() {
+        return mHost.makeChildSurface(null)
+                .setParent(mHost.getSurfaceControl())
+                .setColorLayer()
+                .setName("Dim Layer for - " + mHost.getName())
+                .setCallsite("Dimmer.makeDimLayer")
+                .build();
+    }
+
+    @Override
+    SurfaceControl getDimLayer() {
+        return mDimState != null ? mDimState.mDimLayer : null;
+    }
+
+    @Override
+    void resetDimStates() {
+        if (mDimState == null) {
+            return;
+        }
+        if (!mDimState.mDontReset) {
+            mDimState.mDimming = false;
+        }
+    }
+
+    @Override
+    Rect getDimBounds() {
+        return mDimState != null ? mDimState.mDimBounds : null;
+    }
+
+    @Override
+    void dontAnimateExit() {
+        if (mDimState != null) {
+            mDimState.mAnimateExit = false;
+        }
+    }
+
+    @Override
+    protected void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) {
+        final DimState d = obtainDimState(container);
+
+        if (d == null) {
+            return;
+        }
+
+        // The dim method is called from WindowState.prepareSurfaces(), which is always called
+        // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
+        // relative to the highest Z layer with a dim.
+        SurfaceControl.Transaction t = mHost.getPendingTransaction();
+        t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
+        t.setAlpha(d.mDimLayer, alpha);
+        t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
+
+        d.mDimming = true;
+    }
+
+    @Override
+    boolean updateDims(SurfaceControl.Transaction t) {
+        if (mDimState == null) {
+            return false;
+        }
+
+        if (!mDimState.mDimming) {
+            if (!mDimState.mAnimateExit) {
+                if (mDimState.mDimLayer.isValid()) {
+                    t.remove(mDimState.mDimLayer);
+                }
+            } else {
+                startDimExit(mLastRequestedDimContainer,
+                        mDimState.mSurfaceAnimator, t);
+            }
+            mDimState = null;
+            return false;
+        } else {
+            final Rect bounds = mDimState.mDimBounds;
+            // TODO: Once we use geometry from hierarchy this falls away.
+            t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
+            t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
+            if (!mDimState.mIsVisible) {
+                mDimState.mIsVisible = true;
+                t.show(mDimState.mDimLayer);
+                // Skip enter animation while starting window is on top of its activity
+                final WindowState ws = mLastRequestedDimContainer.asWindowState();
+                if (ws == null || ws.mActivityRecord == null
+                        || ws.mActivityRecord.mStartingData == null) {
+                    startDimEnter(mLastRequestedDimContainer,
+                            mDimState.mSurfaceAnimator, t);
+                }
+            }
+            return true;
+        }
+    }
+
+    private long getDimDuration(WindowContainer container) {
+        // Use the same duration as the animation on the WindowContainer
+        AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
+        final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
+        return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
+                : animationAdapter.getDurationHint();
+    }
+
+    private void startDimEnter(WindowContainer container, SurfaceAnimator animator,
+            SurfaceControl.Transaction t) {
+        startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */);
+    }
+
+    private void startDimExit(WindowContainer container, SurfaceAnimator animator,
+            SurfaceControl.Transaction t) {
+        startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */);
+    }
+
+    private void startAnim(WindowContainer container, SurfaceAnimator animator,
+            SurfaceControl.Transaction t, float startAlpha, float endAlpha) {
+        mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter(
+                        new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)),
+                        mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */,
+                ANIMATION_TYPE_DIMMER);
+    }
+
+    private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec {
+        private final long mDuration;
+        private final float mFromAlpha;
+        private final float mToAlpha;
+
+        AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) {
+            mFromAlpha = fromAlpha;
+            mToAlpha = toAlpha;
+            mDuration = duration;
+        }
+
+        @Override
+        public long getDuration() {
+            return mDuration;
+        }
+
+        @Override
+        public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
+            final float fraction = getFraction(currentPlayTime);
+            final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha;
+            t.setAlpha(sc, alpha);
+        }
+
+        @Override
+        public void dump(PrintWriter pw, String prefix) {
+            pw.print(prefix); pw.print("from="); pw.print(mFromAlpha);
+            pw.print(" to="); pw.print(mToAlpha);
+            pw.print(" duration="); pw.println(mDuration);
+        }
+
+        @Override
+        public void dumpDebugInner(ProtoOutputStream proto) {
+            final long token = proto.start(ALPHA);
+            proto.write(FROM, mFromAlpha);
+            proto.write(TO, mToAlpha);
+            proto.write(DURATION_MS, mDuration);
+            proto.end(token);
+        }
+    }
+}
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/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index 99831d3..23c135a 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -19,6 +19,8 @@
 import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
 import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY;
 
+import static com.android.window.flags.Flags.explicitRefreshRateHints;
+
 import android.hardware.display.DisplayManager;
 import android.view.Display;
 import android.view.Display.Mode;
@@ -137,7 +139,7 @@
         // to run in default refresh rate. But if the display size of default mode is different
         // from the using preferred mode, then still keep the preferred mode to avoid disturbing
         // the animation.
-        if (w.isAnimationRunningSelfOrParent()) {
+        if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) {
             Display.Mode preferredMode = null;
             for (Display.Mode mode : mDisplayInfo.supportedModes) {
                 if (preferredDisplayModeId == mode.getModeId()) {
@@ -251,7 +253,7 @@
 
         // If app is animating, it's not able to control refresh rate because we want the animation
         // to run in default refresh rate.
-        if (w.isAnimationRunningSelfOrParent()) {
+        if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) {
             return w.mFrameRateVote.reset();
         }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index cf6a1fe..7a442e7 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2211,6 +2211,10 @@
                 mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent(
                         organizedTf);
             }
+
+            if (taskDisplayArea.getFocusedRootTask() == rootTask) {
+                taskDisplayArea.clearPreferredTopFocusableRootTask();
+            }
         } finally {
             mService.continueWindowLayout();
             try {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index e6d4866..3775ccd 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -560,6 +560,16 @@
     }
 
     @Override
+    public void reportDecorViewGestureInterceptionChanged(IWindow window, boolean intercepted) {
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mService.reportDecorViewGestureChanged(this, window, intercepted);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
     public void reportKeepClearAreasChanged(IWindow window, List<Rect> restricted,
             List<Rect> unrestricted) {
         if (!mSetsUnrestrictedKeepClearAreas && !unrestricted.isEmpty()) {
diff --git a/services/core/java/com/android/server/wm/SmoothDimmer.java b/services/core/java/com/android/server/wm/SmoothDimmer.java
new file mode 100644
index 0000000..6ddbd2c
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SmoothDimmer.java
@@ -0,0 +1,395 @@
+/*
+ * 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.wm;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER;
+import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
+import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
+import static com.android.server.wm.AlphaAnimationSpecProto.TO;
+import static com.android.server.wm.AnimationSpecProto.ALPHA;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.graphics.Rect;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.io.PrintWriter;
+
+class SmoothDimmer extends Dimmer {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
+    private static final float EPSILON = 0.0001f;
+    // This is in milliseconds.
+    private static final int DEFAULT_DIM_ANIM_DURATION = 200;
+    DimState mDimState;
+    private WindowContainer mLastRequestedDimContainer;
+    private final AnimationAdapterFactory mAnimationAdapterFactory;
+
+    @VisibleForTesting
+    class DimState {
+        /**
+         * The layer where property changes should be invoked on.
+         */
+        SurfaceControl mDimLayer;
+        boolean mDimming;
+        boolean mIsVisible;
+
+        // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
+        final Rect mDimBounds = new Rect();
+
+        /**
+         * Determines whether the dim layer should animate before destroying.
+         */
+        boolean mAnimateExit = true;
+
+        /**
+         * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for
+         * details on Dim lifecycle.
+         */
+        boolean mDontReset;
+
+        Change mCurrentProperties;
+        Change mRequestedProperties;
+        private AnimationSpec mAlphaAnimationSpec;
+        private AnimationAdapter mLocalAnimationAdapter;
+
+        static class Change {
+            private float mAlpha = -1f;
+            private int mBlurRadius = -1;
+            private WindowContainer mDimmingContainer = null;
+            private int mRelativeLayer = -1;
+            private boolean mSkipAnimation = false;
+
+            Change() {}
+
+            Change(Change other) {
+                mAlpha = other.mAlpha;
+                mBlurRadius = other.mBlurRadius;
+                mDimmingContainer = other.mDimmingContainer;
+                mRelativeLayer = other.mRelativeLayer;
+            }
+
+            @Override
+            public String toString() {
+                return "Dim state: alpha=" + mAlpha + ", blur=" + mBlurRadius + ", container="
+                        + mDimmingContainer + ", relativePosition=" + mRelativeLayer
+                        + ", skipAnimation=" + mSkipAnimation;
+            }
+        }
+
+        DimState(SurfaceControl dimLayer) {
+            mDimLayer = dimLayer;
+            mDimming = true;
+            mCurrentProperties = new Change();
+            mRequestedProperties = new Change();
+        }
+
+        void setExitParameters(WindowContainer container) {
+            setRequestedParameters(container, -1, 0, 0);
+        }
+        // Sets a requested change without applying it immediately
+        void setRequestedParameters(WindowContainer container, int relativeLayer, float alpha,
+                int blurRadius) {
+            mRequestedProperties.mDimmingContainer = container;
+            mRequestedProperties.mRelativeLayer = relativeLayer;
+            mRequestedProperties.mAlpha = alpha;
+            mRequestedProperties.mBlurRadius = blurRadius;
+        }
+
+        /**
+         * Commit the last changes we received. Called after
+         * {@link Change#setRequestedParameters(WindowContainer, int, float, int)}
+         */
+        void applyChanges(SurfaceControl.Transaction t) {
+            if (mRequestedProperties.mDimmingContainer.mSurfaceControl == null) {
+                Log.w(TAG, "container " + mRequestedProperties.mDimmingContainer
+                        + "does not have a surface");
+                return;
+            }
+            if (!mDimState.mIsVisible) {
+                mDimState.mIsVisible = true;
+                t.show(mDimState.mDimLayer);
+            }
+            t.setRelativeLayer(mDimLayer,
+                    mRequestedProperties.mDimmingContainer.getSurfaceControl(),
+                    mRequestedProperties.mRelativeLayer);
+
+            if (aspectChanged()) {
+                if (isAnimating()) {
+                    mLocalAnimationAdapter.onAnimationCancelled(mDimLayer);
+                }
+                if (mRequestedProperties.mSkipAnimation
+                        || (!dimmingContainerChanged() && mDimming)) {
+                    // If the dimming container has not changed, then it is running its own
+                    // animation, thus we can directly set the values we get requested, unless it's
+                    // the exiting animation
+                    ProtoLog.d(WM_DEBUG_DIMMER,
+                            "Dim %s skipping animation and directly setting alpha=%f, blur=%d",
+                            mDimLayer, mRequestedProperties.mAlpha,
+                            mRequestedProperties.mBlurRadius);
+                    t.setAlpha(mDimLayer, mRequestedProperties.mAlpha);
+                    t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius);
+                    mRequestedProperties.mSkipAnimation = false;
+                } else {
+                    startAnimation(t);
+                }
+            }
+            mCurrentProperties = new Change(mRequestedProperties);
+        }
+
+        private void startAnimation(SurfaceControl.Transaction t) {
+            mAlphaAnimationSpec = getRequestedAnimationSpec(mRequestedProperties.mAlpha,
+                    mRequestedProperties.mBlurRadius);
+            mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec,
+                    mHost.mWmService.mSurfaceAnimationRunner);
+
+            mLocalAnimationAdapter.startAnimation(mDimLayer, t,
+                    ANIMATION_TYPE_DIMMER, (type, animator) -> {
+                        t.setAlpha(mDimLayer, mRequestedProperties.mAlpha);
+                        t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius);
+                        if (mRequestedProperties.mAlpha == 0f && !mDimming) {
+                            ProtoLog.d(WM_DEBUG_DIMMER,
+                                    "Removing dim surface %s on transaction %s", mDimLayer, t);
+                            t.remove(mDimLayer);
+                        }
+                        mLocalAnimationAdapter = null;
+                        mAlphaAnimationSpec = null;
+                    });
+        }
+
+        private boolean isAnimating() {
+            return mAlphaAnimationSpec != null;
+        }
+
+        private boolean aspectChanged() {
+            return Math.abs(mRequestedProperties.mAlpha - mCurrentProperties.mAlpha) > EPSILON
+                    || mRequestedProperties.mBlurRadius != mCurrentProperties.mBlurRadius;
+        }
+
+        private boolean dimmingContainerChanged() {
+            return mRequestedProperties.mDimmingContainer != mCurrentProperties.mDimmingContainer;
+        }
+
+        private AnimationSpec getRequestedAnimationSpec(float targetAlpha, int targetBlur) {
+            final float startAlpha;
+            final int startBlur;
+            if (mAlphaAnimationSpec != null) {
+                startAlpha = mAlphaAnimationSpec.mCurrentAlpha;
+                startBlur = mAlphaAnimationSpec.mCurrentBlur;
+            } else {
+                startAlpha = Math.max(mCurrentProperties.mAlpha, 0f);
+                startBlur = Math.max(mCurrentProperties.mBlurRadius, 0);
+            }
+            long duration = (long) (getDimDuration(mRequestedProperties.mDimmingContainer)
+                    * Math.abs(targetAlpha - startAlpha));
+
+            ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on dim layer %s, requested by %s, "
+                            + "alpha: %f -> %f, blur: %d -> %d",
+                    mDimLayer, mRequestedProperties.mDimmingContainer, startAlpha, targetAlpha,
+                    startBlur, targetBlur);
+            return new AnimationSpec(
+                    new AnimationExtremes<>(startAlpha, targetAlpha),
+                    new AnimationExtremes<>(startBlur, targetBlur),
+                    duration
+            );
+        }
+    }
+
+    protected SmoothDimmer(WindowContainer host) {
+        this(host, new AnimationAdapterFactory());
+    }
+
+    @VisibleForTesting
+    SmoothDimmer(WindowContainer host, AnimationAdapterFactory animationFactory) {
+        super(host);
+        mAnimationAdapterFactory = animationFactory;
+    }
+
+    private DimState obtainDimState(WindowContainer container) {
+        if (mDimState == null) {
+            try {
+                final SurfaceControl ctl = makeDimLayer();
+                mDimState = new DimState(ctl);
+            } catch (Surface.OutOfResourcesException e) {
+                Log.w(TAG, "OutOfResourcesException creating dim surface");
+            }
+        }
+
+        mLastRequestedDimContainer = container;
+        return mDimState;
+    }
+
+    private SurfaceControl makeDimLayer() {
+        return mHost.makeChildSurface(null)
+                .setParent(mHost.getSurfaceControl())
+                .setColorLayer()
+                .setName("Dim Layer for - " + mHost.getName())
+                .setCallsite("Dimmer.makeDimLayer")
+                .build();
+    }
+
+    @Override
+    SurfaceControl getDimLayer() {
+        return mDimState != null ? mDimState.mDimLayer : null;
+    }
+
+    @Override
+    void resetDimStates() {
+        if (mDimState == null) {
+            return;
+        }
+        if (!mDimState.mDontReset) {
+            mDimState.mDimming = false;
+        }
+    }
+
+    @Override
+    Rect getDimBounds() {
+        return mDimState != null ? mDimState.mDimBounds : null;
+    }
+
+    @Override
+    void dontAnimateExit() {
+        if (mDimState != null) {
+            mDimState.mAnimateExit = false;
+        }
+    }
+
+    @Override
+    protected void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) {
+        final DimState d = obtainDimState(container);
+
+        mDimState.mRequestedProperties.mDimmingContainer = container;
+        mDimState.setRequestedParameters(container, relativeLayer, alpha, blurRadius);
+        d.mDimming = true;
+    }
+
+    boolean updateDims(SurfaceControl.Transaction t) {
+        if (mDimState == null) {
+            return false;
+        }
+
+        if (!mDimState.mDimming) {
+            // No one is dimming anymore, fade out dim and remove
+            if (!mDimState.mAnimateExit) {
+                if (mDimState.mDimLayer.isValid()) {
+                    t.remove(mDimState.mDimLayer);
+                }
+            } else {
+                mDimState.setExitParameters(
+                        mDimState.mRequestedProperties.mDimmingContainer);
+                mDimState.applyChanges(t);
+            }
+            mDimState = null;
+            return false;
+        }
+        final Rect bounds = mDimState.mDimBounds;
+        // TODO: Once we use geometry from hierarchy this falls away.
+        t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
+        t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
+        // Skip enter animation while starting window is on top of its activity
+        final WindowState ws = mLastRequestedDimContainer.asWindowState();
+        if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null
+                && ws.mActivityRecord.mStartingData != null) {
+            mDimState.mRequestedProperties.mSkipAnimation = true;
+        }
+        mDimState.applyChanges(t);
+        return true;
+    }
+
+    private long getDimDuration(WindowContainer container) {
+        // Use the same duration as the animation on the WindowContainer
+        AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
+        final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
+        return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
+                : animationAdapter.getDurationHint();
+    }
+
+    private static class AnimationExtremes<T> {
+        final T mStartValue;
+        final T mFinishValue;
+
+        AnimationExtremes(T fromValue, T toValue) {
+            mStartValue = fromValue;
+            mFinishValue = toValue;
+        }
+    }
+
+    private static class AnimationSpec implements LocalAnimationAdapter.AnimationSpec {
+        private final long mDuration;
+        private final AnimationExtremes<Float> mAlpha;
+        private final AnimationExtremes<Integer> mBlur;
+
+        float mCurrentAlpha = 0;
+        int mCurrentBlur = 0;
+
+        AnimationSpec(AnimationExtremes<Float> alpha,
+                AnimationExtremes<Integer> blur, long duration) {
+            mAlpha = alpha;
+            mBlur = blur;
+            mDuration = duration;
+        }
+
+        @Override
+        public long getDuration() {
+            return mDuration;
+        }
+
+        @Override
+        public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
+            final float fraction = getFraction(currentPlayTime);
+            mCurrentAlpha =
+                    fraction * (mAlpha.mFinishValue - mAlpha.mStartValue) + mAlpha.mStartValue;
+            mCurrentBlur =
+                    (int) fraction * (mBlur.mFinishValue - mBlur.mStartValue) + mBlur.mStartValue;
+            t.setAlpha(sc, mCurrentAlpha);
+            t.setBackgroundBlurRadius(sc, mCurrentBlur);
+        }
+
+        @Override
+        public void dump(PrintWriter pw, String prefix) {
+            pw.print(prefix); pw.print("from_alpha="); pw.print(mAlpha.mStartValue);
+            pw.print(" to_alpha="); pw.print(mAlpha.mFinishValue);
+            pw.print(prefix); pw.print("from_blur="); pw.print(mBlur.mStartValue);
+            pw.print(" to_blur="); pw.print(mBlur.mFinishValue);
+            pw.print(" duration="); pw.println(mDuration);
+        }
+
+        @Override
+        public void dumpDebugInner(ProtoOutputStream proto) {
+            final long token = proto.start(ALPHA);
+            proto.write(FROM, mAlpha.mStartValue);
+            proto.write(TO, mAlpha.mFinishValue);
+            proto.write(DURATION_MS, mDuration);
+            proto.end(token);
+        }
+    }
+
+    static class AnimationAdapterFactory {
+
+        public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec,
+                SurfaceAnimationRunner runner) {
+            return new LocalAnimationAdapter(alphaAnimationSpec, runner);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 408ea6e..c3de4d5 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -49,7 +49,8 @@
  * {@link AnimationAdapter}. When the animation is done animating, our callback to finish the
  * animation will be invoked, at which we reparent the children back to the original parent.
  */
-class SurfaceAnimator {
+@VisibleForTesting
+public class SurfaceAnimator {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "SurfaceAnimator" : TAG_WM;
 
@@ -617,7 +618,8 @@
      * Callback to be passed into {@link AnimationAdapter#startAnimation} to be invoked by the
      * component that is running the animation when the animation is finished.
      */
-    interface OnAnimationFinishedCallback {
+    @VisibleForTesting
+    public interface OnAnimationFinishedCallback {
         void onAnimationFinished(@AnimationType int type, AnimationAdapter anim);
     }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8385615..7b8acea 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2858,7 +2858,8 @@
     }
 
     /** Bounds of the task to be used for dimming, as well as touch related tests. */
-    void getDimBounds(Rect out) {
+    @Override
+    void getDimBounds(@NonNull Rect out) {
         if (isRootTask()) {
             getBounds(out);
             return;
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 50bc825..52e9f8d 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -103,6 +103,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.am.HostingRecord;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.window.flags.Flags;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -209,7 +210,26 @@
      */
     int mMinHeight;
 
-    Dimmer mDimmer = new Dimmer(this);
+    Dimmer mDimmer = Flags.dimmerRefactor()
+            ? new SmoothDimmer(this) : new LegacyDimmer(this);
+
+    /** Apply the dim layer on the embedded TaskFragment. */
+    static final int EMBEDDED_DIM_AREA_TASK_FRAGMENT = 0;
+
+    /** Apply the dim layer on the parent Task for an embedded TaskFragment. */
+    static final int EMBEDDED_DIM_AREA_PARENT_TASK = 1;
+
+    /**
+     * The type of dim layer area for an embedded TaskFragment.
+     */
+    @IntDef(prefix = {"EMBEDDED_DIM_AREA_"}, value = {
+            EMBEDDED_DIM_AREA_TASK_FRAGMENT,
+            EMBEDDED_DIM_AREA_PARENT_TASK,
+    })
+    @interface EmbeddedDimArea {}
+
+    @EmbeddedDimArea
+    private int mEmbeddedDimArea = EMBEDDED_DIM_AREA_TASK_FRAGMENT;
 
     /** This task fragment will be removed when the cleanup of its children are done. */
     private boolean mIsRemovalRequested;
@@ -2929,14 +2949,27 @@
 
     @Override
     Dimmer getDimmer() {
-        // If the window is in an embedded TaskFragment, we want to dim at the TaskFragment.
-        if (asTask() == null) {
+        // If this is in an embedded TaskFragment and we want the dim applies on the TaskFragment.
+        if (mIsEmbedded && mEmbeddedDimArea == EMBEDDED_DIM_AREA_TASK_FRAGMENT) {
             return mDimmer;
         }
 
         return super.getDimmer();
     }
 
+    /** Bounds to be used for dimming, as well as touch related tests. */
+    void getDimBounds(@NonNull Rect out) {
+        if (mIsEmbedded && mEmbeddedDimArea == EMBEDDED_DIM_AREA_PARENT_TASK) {
+            out.set(getTask().getBounds());
+        } else {
+            out.set(getBounds());
+        }
+    }
+
+    void setEmbeddedDimArea(@EmbeddedDimArea int embeddedDimArea) {
+        mEmbeddedDimArea = embeddedDimArea;
+    }
+
     @Override
     void prepareSurfaces() {
         if (asTask() != null) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 882104a..f3fb7c4 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -703,6 +703,7 @@
         if (dc == null || mTargetDisplays.contains(dc)) return;
         mTargetDisplays.add(dc);
         addOnTopTasks(dc, mOnTopTasksStart);
+        mController.startPerfHintForDisplay(dc.mDisplayId);
     }
 
     /**
@@ -1391,7 +1392,6 @@
             dc.handleCompleteDeferredRemoval();
         }
         validateKeyguardOcclusion();
-        validateVisibility();
 
         mState = STATE_FINISHED;
         // Rotation change may be deferred while there is a display change transition, so check
@@ -2766,29 +2766,6 @@
         }
     }
 
-    private void validateVisibility() {
-        for (int i = mTargets.size() - 1; i >= 0; --i) {
-            if (reduceMode(mTargets.get(i).mReadyMode) != TRANSIT_CLOSE) {
-                return;
-            }
-        }
-        // All modes are CLOSE. The surfaces may be hidden by the animation unexpectedly.
-        // If the window container should be visible, then recover it.
-        mController.mStateValidators.add(() -> {
-            for (int i = mTargets.size() - 1; i >= 0; --i) {
-                final ChangeInfo change = mTargets.get(i);
-                if (!change.mContainer.isVisibleRequested()
-                        || change.mContainer.mSurfaceControl == null) {
-                    continue;
-                }
-                Slog.e(TAG, "Force show for visible " + change.mContainer
-                        + " which may be hidden by transition unexpectedly");
-                change.mContainer.getSyncTransaction().show(change.mContainer.mSurfaceControl);
-                change.mContainer.scheduleAnimation();
-            }
-        });
-    }
-
     /**
      * Returns {@code true} if the transition and the corresponding transaction should be applied
      * on display thread. Currently, this only checks for display rotation change because the order
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 78afaa8..de7871e 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -22,8 +22,10 @@
 import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.SystemPerformanceHinter.HINT_SF;
 
 import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
+import static com.android.window.flags.Flags.explicitRefreshRateHints;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -39,6 +41,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
@@ -48,6 +51,8 @@
 import android.window.ITransitionMetricsReporter;
 import android.window.ITransitionPlayer;
 import android.window.RemoteTransition;
+import android.window.SystemPerformanceHinter;
+import android.window.SystemPerformanceHinter.HighPerfSession;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
@@ -125,6 +130,8 @@
     SnapshotController mSnapshotController;
     TransitionTracer mTransitionTracer;
 
+    private SystemPerformanceHinter mSystemPerformanceHinter;
+
     private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners =
             new ArrayList<>();
 
@@ -176,6 +183,24 @@
 
     private final IBinder.DeathRecipient mTransitionPlayerDeath;
 
+    /**
+     * Tracks active perf sessions that boost frame rate and hint sf to increase its
+     * estimated work duration.
+     */
+    private final ArraySet<HighPerfSession> mHighPerfSessions = new ArraySet<>();
+
+
+    /**
+     * Starts a perf hint session which will boost the refresh rate for the display and change
+     * sf duration to handle larger workloads.
+     */
+    void startPerfHintForDisplay(int displayId) {
+        if (explicitRefreshRateHints()) {
+            mHighPerfSessions.add(mSystemPerformanceHinter.startSession(HINT_SF, displayId,
+                    "Transition collected"));
+        }
+    }
+
     static class QueuedTransition {
         final Transition mTransition;
         final OnStartCollect mOnStartCollect;
@@ -255,6 +280,12 @@
         mIsWaitingForDisplayEnabled = !wms.mDisplayEnabled;
         registerLegacyListener(wms.mActivityManagerAppTransitionNotifier);
         setSyncEngine(wms.mSyncEngine);
+        setSystemPerformanceHinter(wms.mSystemPerformanceHinter);
+    }
+
+    @VisibleForTesting
+    void setSystemPerformanceHinter(SystemPerformanceHinter hinter) {
+        mSystemPerformanceHinter = hinter;
     }
 
     @VisibleForTesting
@@ -960,6 +991,36 @@
         mValidateDisplayVis.clear();
     }
 
+    void onVisibleWithoutCollectingTransition(WindowContainer<?> wc, String caller) {
+        final boolean isPlaying = !mPlayingTransitions.isEmpty();
+        Slog.e(TAG, "Set visible without transition " + wc + " playing=" + isPlaying
+                + " caller=" + caller);
+        if (!isPlaying) {
+            enforceSurfaceVisible(wc);
+            return;
+        }
+        // Update surface visibility after the playing transitions are finished, so the last
+        // visibility won't be replaced by the finish transaction of transition.
+        mStateValidators.add(() -> {
+            if (wc.isVisibleRequested()) {
+                enforceSurfaceVisible(wc);
+            }
+        });
+    }
+
+    private void enforceSurfaceVisible(WindowContainer<?> wc) {
+        if (wc.mSurfaceControl == null) return;
+        wc.getSyncTransaction().show(wc.mSurfaceControl);
+        // Force showing the parents because they may be hidden by previous transition.
+        for (WindowContainer<?> p = wc.getParent(); p != null && p != wc.mDisplayContent;
+                p = p.getParent()) {
+            if (p.mSurfaceControl != null) {
+                p.getSyncTransaction().show(p.mSurfaceControl);
+            }
+        }
+        wc.scheduleAnimation();
+    }
+
     /**
      * Called when the transition has a complete set of participants for its operation. In other
      * words, it is when the transition is "ready" but is still waiting for participants to draw.
@@ -1006,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) {
@@ -1164,18 +1233,27 @@
         final boolean animatingState = !mPlayingTransitions.isEmpty()
                     || (mCollectingTransition != null && mCollectingTransition.isStarted());
         if (animatingState && !mAnimatingState) {
-            t.setEarlyWakeupStart();
+            if (!explicitRefreshRateHints()) {
+                t.setEarlyWakeupStart();
+            }
             // Usually transitions put quite a load onto the system already (with all the things
             // happening in app), so pause task snapshot persisting to not increase the load.
             mSnapshotController.setPause(true);
             mAnimatingState = true;
             Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */);
         } else if (!animatingState && mAnimatingState) {
-            t.setEarlyWakeupEnd();
+            if (!explicitRefreshRateHints()) {
+                t.setEarlyWakeupEnd();
+            }
             mAtm.mWindowManager.scheduleAnimationLocked();
             mSnapshotController.setPause(false);
             mAnimatingState = false;
             Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */);
+            // We close all perf sessions here when all transitions finish. The sessions are created
+            // when we collect transitions because we have access to the display id.
+            for (HighPerfSession perfSession : mHighPerfSessions) {
+                perfSession.close();
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 7e5dabb..674ff48 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -85,13 +85,8 @@
     // to another, and this is the previous wallpaper target.
     private WindowState mPrevWallpaperTarget = null;
 
-    private float mLastWallpaperX = -1;
-    private float mLastWallpaperY = -1;
-    private float mLastWallpaperXStep = -1;
-    private float mLastWallpaperYStep = -1;
     private float mLastWallpaperZoomOut = 0;
-    private int mLastWallpaperDisplayOffsetX = Integer.MIN_VALUE;
-    private int mLastWallpaperDisplayOffsetY = Integer.MIN_VALUE;
+
     // Whether COMMAND_FREEZE was dispatched.
     private boolean mLastFrozen = false;
 
@@ -116,8 +111,6 @@
     private static final int WALLPAPER_DRAW_TIMEOUT = 2;
     private int mWallpaperDrawState = WALLPAPER_DRAW_NORMAL;
 
-    private boolean mShouldUpdateZoom;
-
     @Nullable private Point mLargestDisplaySize = null;
 
     private final FindWallpaperTargetResult mFindResults = new FindWallpaperTargetResult();
@@ -370,6 +363,7 @@
         // Full size of the wallpaper (usually larger than bounds above to parallax scroll when
         // swiping through Launcher pages).
         final Rect wallpaperFrame = wallpaperWin.getFrame();
+        WallpaperWindowToken token = wallpaperWin.mToken.asWallpaperToken();
 
         final int diffWidth = wallpaperFrame.width() - lastWallpaperBounds.width();
         final int diffHeight = wallpaperFrame.height() - lastWallpaperBounds.height();
@@ -394,10 +388,10 @@
         // The 0 to 1 scale is because the "length" varies depending on how many home screens you
         // have, so 0 is the left of the first home screen, and 1 is the right of the last one (for
         // LTR, and the opposite for RTL).
-        float wpx = mLastWallpaperX >= 0 ? mLastWallpaperX : defaultWallpaperX;
+        float wpx = token.mWallpaperX >= 0 ? token.mWallpaperX : defaultWallpaperX;
         // "Wallpaper X step size" is how much of that 0-1 is one "page" of the home screen
         // when scrolling.
-        float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f;
+        float wpxs = token.mWallpaperXStep >= 0 ? token.mWallpaperXStep : -1.0f;
         // Difference between width of wallpaper image, and the last size of the wallpaper.
         // This is the horizontal surplus from the prior configuration.
         int availw = diffWidth;
@@ -406,10 +400,10 @@
                 wallpaperWin.isRtl());
         availw -= displayOffset;
         int offset = availw > 0 ? -(int)(availw * wpx + .5f) : 0;
-        if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
+        if (token.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
             // if device is LTR, then offset wallpaper to the left (the wallpaper is drawn
             // always starting from the left of the screen).
-            offset += mLastWallpaperDisplayOffsetX;
+            offset += token.mWallpaperDisplayOffsetX;
         } else if (!wallpaperWin.isRtl()) {
             // In RTL the offset is calculated so that the wallpaper ends up right aligned (see
             // offset above).
@@ -423,11 +417,11 @@
             rawChanged = true;
         }
 
-        float wpy = mLastWallpaperY >= 0 ? mLastWallpaperY : 0.5f;
-        float wpys = mLastWallpaperYStep >= 0 ? mLastWallpaperYStep : -1.0f;
+        float wpy = token.mWallpaperY >= 0 ? token.mWallpaperY : 0.5f;
+        float wpys = token.mWallpaperYStep >= 0 ? token.mWallpaperYStep : -1.0f;
         offset = diffHeight > 0 ? -(int) (diffHeight * wpy + .5f) : 0;
-        if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
-            offset += mLastWallpaperDisplayOffsetY;
+        if (token.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
+            offset += token.mWallpaperDisplayOffsetY;
         }
         newYOffset = offset;
 
@@ -549,8 +543,10 @@
     void setWallpaperZoomOut(WindowState window, float zoom) {
         if (Float.compare(window.mWallpaperZoomOut, zoom) != 0) {
             window.mWallpaperZoomOut = zoom;
-            mShouldUpdateZoom = true;
-            updateWallpaperOffsetLocked(window, false);
+            computeLastWallpaperZoomOut();
+            for (WallpaperWindowToken token : mWallpaperTokens) {
+                token.updateWallpaperOffset(false);
+            }
         }
     }
 
@@ -598,43 +594,48 @@
             // zoom effect from home.
             target = changingTarget;
         }
-        if (target != null) {
-            if (target.mWallpaperX >= 0) {
-                mLastWallpaperX = target.mWallpaperX;
-            } else if (changingTarget.mWallpaperX >= 0) {
-                mLastWallpaperX = changingTarget.mWallpaperX;
-            }
-            if (target.mWallpaperY >= 0) {
-                mLastWallpaperY = target.mWallpaperY;
-            } else if (changingTarget.mWallpaperY >= 0) {
-                mLastWallpaperY = changingTarget.mWallpaperY;
-            }
-            computeLastWallpaperZoomOut();
-            if (target.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
-                mLastWallpaperDisplayOffsetX = target.mWallpaperDisplayOffsetX;
-            } else if (changingTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
-                mLastWallpaperDisplayOffsetX = changingTarget.mWallpaperDisplayOffsetX;
-            }
-            if (target.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
-                mLastWallpaperDisplayOffsetY = target.mWallpaperDisplayOffsetY;
-            } else if (changingTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
-                mLastWallpaperDisplayOffsetY = changingTarget.mWallpaperDisplayOffsetY;
-            }
-            if (target.mWallpaperXStep >= 0) {
-                mLastWallpaperXStep = target.mWallpaperXStep;
-            } else if (changingTarget.mWallpaperXStep >= 0) {
-                mLastWallpaperXStep = changingTarget.mWallpaperXStep;
-            }
-            if (target.mWallpaperYStep >= 0) {
-                mLastWallpaperYStep = target.mWallpaperYStep;
-            } else if (changingTarget.mWallpaperYStep >= 0) {
-                mLastWallpaperYStep = changingTarget.mWallpaperYStep;
-            }
-        }
 
-        for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
-            mWallpaperTokens.get(curTokenNdx).updateWallpaperOffset(sync);
+        WallpaperWindowToken token = getTokenForTarget(target);
+        if (token == null) return;
+
+        if (target.mWallpaperX >= 0) {
+            token.mWallpaperX = target.mWallpaperX;
+        } else if (changingTarget.mWallpaperX >= 0) {
+            token.mWallpaperX = changingTarget.mWallpaperX;
         }
+        if (target.mWallpaperY >= 0) {
+            token.mWallpaperY = target.mWallpaperY;
+        } else if (changingTarget.mWallpaperY >= 0) {
+            token.mWallpaperY = changingTarget.mWallpaperY;
+        }
+        if (target.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
+            token.mWallpaperDisplayOffsetX = target.mWallpaperDisplayOffsetX;
+        } else if (changingTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
+            token.mWallpaperDisplayOffsetX = changingTarget.mWallpaperDisplayOffsetX;
+        }
+        if (target.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
+            token.mWallpaperDisplayOffsetY = target.mWallpaperDisplayOffsetY;
+        } else if (changingTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
+            token.mWallpaperDisplayOffsetY = changingTarget.mWallpaperDisplayOffsetY;
+        }
+        if (target.mWallpaperXStep >= 0) {
+            token.mWallpaperXStep = target.mWallpaperXStep;
+        } else if (changingTarget.mWallpaperXStep >= 0) {
+            token.mWallpaperXStep = changingTarget.mWallpaperXStep;
+        }
+        if (target.mWallpaperYStep >= 0) {
+            token.mWallpaperYStep = target.mWallpaperYStep;
+        } else if (changingTarget.mWallpaperYStep >= 0) {
+            token.mWallpaperYStep = changingTarget.mWallpaperYStep;
+        }
+        token.updateWallpaperOffset(sync);
+    }
+
+    private WallpaperWindowToken getTokenForTarget(WindowState target) {
+        if (target == null) return null;
+        WindowState window = mFindResults.getTopWallpaper(
+                target.canShowWhenLocked() && mService.isKeyguardLocked());
+        return window == null ? null : window.mToken.asWallpaperToken();
     }
 
     void clearLastWallpaperTimeoutTime() {
@@ -805,10 +806,11 @@
         // all wallpapers go behind it.
         findWallpaperTarget();
         updateWallpaperWindowsTarget(mFindResults);
+        WallpaperWindowToken token = getTokenForTarget(mWallpaperTarget);
 
         // The window is visible to the compositor...but is it visible to the user?
         // That is what the wallpaper cares about.
-        final boolean visible = mWallpaperTarget != null;
+        final boolean visible = token != null;
         if (DEBUG_WALLPAPER) {
             Slog.v(TAG, "Wallpaper visibility: " + visible + " at display "
                     + mDisplayContent.getDisplayId());
@@ -816,19 +818,18 @@
 
         if (visible) {
             if (mWallpaperTarget.mWallpaperX >= 0) {
-                mLastWallpaperX = mWallpaperTarget.mWallpaperX;
-                mLastWallpaperXStep = mWallpaperTarget.mWallpaperXStep;
+                token.mWallpaperX = mWallpaperTarget.mWallpaperX;
+                token.mWallpaperXStep = mWallpaperTarget.mWallpaperXStep;
             }
-            computeLastWallpaperZoomOut();
             if (mWallpaperTarget.mWallpaperY >= 0) {
-                mLastWallpaperY = mWallpaperTarget.mWallpaperY;
-                mLastWallpaperYStep = mWallpaperTarget.mWallpaperYStep;
+                token.mWallpaperY = mWallpaperTarget.mWallpaperY;
+                token.mWallpaperYStep = mWallpaperTarget.mWallpaperYStep;
             }
             if (mWallpaperTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
-                mLastWallpaperDisplayOffsetX = mWallpaperTarget.mWallpaperDisplayOffsetX;
+                token.mWallpaperDisplayOffsetX = mWallpaperTarget.mWallpaperDisplayOffsetX;
             }
             if (mWallpaperTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
-                mLastWallpaperDisplayOffsetY = mWallpaperTarget.mWallpaperDisplayOffsetY;
+                token.mWallpaperDisplayOffsetY = mWallpaperTarget.mWallpaperDisplayOffsetY;
             }
         }
 
@@ -1020,13 +1021,11 @@
      * we'll have conflicts and break the "depth system" mental model.
      */
     private void computeLastWallpaperZoomOut() {
-        if (mShouldUpdateZoom) {
-            mLastWallpaperZoomOut = 0;
-            mDisplayContent.forAllWindows(mComputeMaxZoomOutFunction, true);
-            mShouldUpdateZoom = false;
-        }
+        mLastWallpaperZoomOut = 0;
+        mDisplayContent.forAllWindows(mComputeMaxZoomOutFunction, true);
     }
 
+
     private float zoomOutToScale(float zoomOut) {
         return MathUtils.lerp(mMinWallpaperScale, mMaxWallpaperScale, 1 - zoomOut);
     }
@@ -1034,19 +1033,28 @@
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("displayId="); pw.println(mDisplayContent.getDisplayId());
         pw.print(prefix); pw.print("mWallpaperTarget="); pw.println(mWallpaperTarget);
+        pw.print(prefix); pw.print("mLastWallpaperZoomOut="); pw.println(mLastWallpaperZoomOut);
         if (mPrevWallpaperTarget != null) {
             pw.print(prefix); pw.print("mPrevWallpaperTarget="); pw.println(mPrevWallpaperTarget);
         }
-        pw.print(prefix); pw.print("mLastWallpaperX="); pw.print(mLastWallpaperX);
-        pw.print(" mLastWallpaperY="); pw.println(mLastWallpaperY);
-        if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE
-                || mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
-            pw.print(prefix);
-            pw.print("mLastWallpaperDisplayOffsetX="); pw.print(mLastWallpaperDisplayOffsetX);
-            pw.print(" mLastWallpaperDisplayOffsetY="); pw.println(mLastWallpaperDisplayOffsetY);
+
+        for (WallpaperWindowToken t : mWallpaperTokens) {
+            pw.print(prefix); pw.println("token " + t + ":");
+            pw.print(prefix); pw.print("  canShowWhenLocked="); pw.println(t.canShowWhenLocked());
+            dumpValue(pw, prefix, "mWallpaperX", t.mWallpaperX);
+            dumpValue(pw, prefix, "mWallpaperY", t.mWallpaperY);
+            dumpValue(pw, prefix, "mWallpaperXStep", t.mWallpaperXStep);
+            dumpValue(pw, prefix, "mWallpaperYStep", t.mWallpaperYStep);
+            dumpValue(pw, prefix, "mWallpaperDisplayOffsetX", t.mWallpaperDisplayOffsetX);
+            dumpValue(pw, prefix, "mWallpaperDisplayOffsetY", t.mWallpaperDisplayOffsetY);
         }
     }
 
+    private void dumpValue(PrintWriter pw, String prefix, String valueName, float value) {
+        pw.print(prefix); pw.print("  " + valueName + "=");
+        pw.println(value >= 0 ? value : "NA");
+    }
+
     /** Helper class for storing the results of a wallpaper target find operation. */
     final private static class FindWallpaperTargetResult {
 
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index c7fd147..50ef52a 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -42,6 +42,12 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperWindowToken" : TAG_WM;
 
     private boolean mShowWhenLocked = false;
+    float mWallpaperX = -1;
+    float mWallpaperY = -1;
+    float mWallpaperXStep = -1;
+    float mWallpaperYStep = -1;
+    int mWallpaperDisplayOffsetX = Integer.MIN_VALUE;
+    int mWallpaperDisplayOffsetY = Integer.MIN_VALUE;
 
     WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit,
             DisplayContent dc, boolean ownerCanManageAppTokens) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
new file mode 100644
index 0000000..5b9acb2
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -0,0 +1,51 @@
+/*
+ * 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.wm;
+
+import com.android.window.flags.Flags;
+
+/**
+ * Utility class to read the flags used in the WindowManager server.
+ *
+ * It is not very cheap to read trunk stable flag, so having a centralized place to cache the flag
+ * values in the system server side.
+ *
+ * Flags should be defined in `core.java.android.window.flags` to allow access from client side.
+ *
+ * To override flag:
+ *   adb shell device_config put [namespace] [package].[name] [true/false]
+ *   adb reboot
+ *
+ * To access in wm:
+ *   {@link WindowManagerService#mFlags}
+ *
+ * Notes:
+ *   The system may use flags at anytime, so changing flags will only take effect after device
+ *   reboot. Otherwise, it may result unexpected behavior, such as broken transition.
+ *   When a flag needs to be read from both the server side and the client side, changing the flag
+ *   value will result difference in server and client until device reboot.
+ */
+class WindowManagerFlags {
+
+    /* Start Available Flags */
+
+    final boolean mSyncWindowConfigUpdateFlag = Flags.syncWindowConfigUpdateFlag();
+
+    final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag();
+
+    /* End Available Flags */
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 074b404..9663f3a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -249,6 +249,7 @@
 import android.view.Gravity;
 import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.ICrossWindowBlurEnabledListener;
+import android.view.IDecorViewGestureListener;
 import android.view.IDisplayChangeWindowController;
 import android.view.IDisplayFoldListener;
 import android.view.IDisplayWindowInsetsController;
@@ -303,6 +304,7 @@
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
 import android.window.ScreenCapture;
+import android.window.SystemPerformanceHinter;
 import android.window.TaskSnapshot;
 import android.window.WindowContainerToken;
 import android.window.WindowContextInfo;
@@ -546,6 +548,8 @@
     @VisibleForTesting
     WindowManagerPolicy mPolicy;
 
+    final WindowManagerFlags mFlags;
+
     final IActivityManager mActivityManager;
     final ActivityManagerInternal mAmInternal;
     final UserManagerInternal mUmInternal;
@@ -1035,6 +1039,8 @@
         sThreadPriorityBooster.reset();
     }
 
+    SystemPerformanceHinter mSystemPerformanceHinter;
+
     void openSurfaceTransaction() {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "openSurfaceTransaction");
@@ -1152,6 +1158,7 @@
         mGlobalLock = atm.getGlobalLock();
         mAtmService = atm;
         mContext = context;
+        mFlags = new WindowManagerFlags();
         mIsPc = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
         mAllowBootMessages = showBootMsgs;
         mLimitedAlphaCompositing = context.getResources().getBoolean(
@@ -1328,6 +1335,13 @@
         mBlurController = new BlurController(mContext, mPowerManager);
         mTaskFpsCallbackController = new TaskFpsCallbackController(mContext);
         mAccessibilityController = new AccessibilityController(this);
+        mSystemPerformanceHinter = new SystemPerformanceHinter(mContext, displayId -> {
+            synchronized (mGlobalLock) {
+                DisplayContent dc = mRoot.getDisplayContent(displayId);
+                return (dc == null) ? null : dc.getSurfaceControl();
+            }
+
+        }, mTransactionFactory);
     }
 
     DisplayAreaPolicy.Provider getDisplayAreaPolicyProvider() {
@@ -2247,6 +2261,7 @@
                     }
                 }
 
+                final boolean wasTrustedOverlay = win.isWindowTrustedOverlay();
                 flagChanges = win.mAttrs.flags ^ attrs.flags;
                 privateFlagChanges = win.mAttrs.privateFlags ^ attrs.privateFlags;
                 attrChanges = win.mAttrs.copyFrom(attrs);
@@ -2259,6 +2274,9 @@
                 if (layoutChanged && win.providesDisplayDecorInsets()) {
                     configChanged = displayPolicy.updateDecorInsetsInfo();
                 }
+                if (wasTrustedOverlay != win.isWindowTrustedOverlay()) {
+                    win.updateTrustedOverlay();
+                }
                 if (win.mActivityRecord != null && ((flagChanges & FLAG_SHOW_WHEN_LOCKED) != 0
                         || (flagChanges & FLAG_DISMISS_KEYGUARD) != 0)) {
                     win.mActivityRecord.checkKeyguardFlagsChanged();
@@ -3100,10 +3118,15 @@
 
     @Override
     public void notifyKeyguardTrustedChanged() {
-        synchronized (mGlobalLock) {
-            if (mAtmService.mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) {
-                mRoot.ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                if (mAtmService.mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) {
+                    mRoot.ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+                }
             }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
         }
     }
 
@@ -4629,8 +4652,9 @@
         synchronized (mGlobalLock) {
             final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
             if (displayContent == null) {
-                throw new IllegalArgumentException("Trying to register visibility event "
-                        + "for invalid display: " + displayId);
+                throw new IllegalArgumentException(
+                        "Trying to register system gesture exclusion event for invalid display: "
+                                + displayId);
             }
             displayContent.registerSystemGestureExclusionListener(listener);
         }
@@ -4642,13 +4666,64 @@
         synchronized (mGlobalLock) {
             final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
             if (displayContent == null) {
-                throw new IllegalArgumentException("Trying to register visibility event "
-                        + "for invalid display: " + displayId);
+                throw new IllegalArgumentException(
+                        "Trying to unregister system gesture exclusion event for invalid display: "
+                                + displayId);
             }
             displayContent.unregisterSystemGestureExclusionListener(listener);
         }
     }
 
+    @Override
+    public void registerDecorViewGestureListener(
+            IDecorViewGestureListener listener, int displayId) {
+        if (!checkCallingPermission(android.Manifest.permission.MONITOR_INPUT,
+                "registerDecorViewGestureListener()")) {
+            throw new SecurityException("Requires MONITOR_INPUT permission");
+        }
+        synchronized (mGlobalLock) {
+            final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+            if (displayContent == null) {
+                throw new IllegalArgumentException(
+                        "Trying to register DecorView gesture event listener"
+                                + "for invalid display: "
+                                + displayId);
+            }
+            displayContent.registerDecorViewGestureListener(listener);
+        }
+    }
+
+    @Override
+    public void unregisterDecorViewGestureListener(
+            IDecorViewGestureListener listener, int displayId) {
+        if (!checkCallingPermission(android.Manifest.permission.MONITOR_INPUT,
+                "unregisterSystemGestureExclusionListener()")) {
+            throw new SecurityException("Requires MONITOR_INPUT permission");
+        }
+        synchronized (mGlobalLock) {
+            final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+            if (displayContent == null) {
+                throw new IllegalArgumentException(
+                        "Trying to unregister DecorView gesture event listener"
+                                + "for invalid display: "
+                                + displayId);
+            }
+            displayContent.unregisterDecorViewGestureListener(listener);
+        }
+    }
+
+    void reportDecorViewGestureChanged(Session session, IWindow window, boolean intercepted) {
+        synchronized (mGlobalLock) {
+            final WindowState win =
+                    windowForClientLocked(session, window, false /* throwOnError */);
+            if (win == null) {
+                return;
+            }
+            win.getDisplayContent()
+                    .updateDecorViewGestureIntercepted(win.mToken.token, intercepted);
+        }
+    }
+
     void reportSystemGestureExclusionChanged(Session session, IWindow window,
             List<Rect> exclusionRects) {
         synchronized (mGlobalLock) {
@@ -5228,7 +5303,11 @@
     public void displayReady() {
         synchronized (mGlobalLock) {
             if (mMaxUiWidth > 0) {
-                mRoot.forAllDisplays(displayContent -> displayContent.setMaxUiWidth(mMaxUiWidth));
+                mRoot.forAllDisplays(dc -> {
+                    if (dc.mDisplay.getType() == Display.TYPE_INTERNAL) {
+                        dc.setMaxUiWidth(mMaxUiWidth);
+                    }
+                });
             }
             applyForcedPropertiesForDefaultDisplay();
             mAnimator.ready();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4beec2b..7f36aec 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;
@@ -1188,7 +1189,20 @@
         }
     }
 
-    public boolean isWindowTrustedOverlay() {
+    @Override
+    void setInitialSurfaceControlProperties(SurfaceControl.Builder b) {
+        super.setInitialSurfaceControlProperties(b);
+        if (surfaceTrustedOverlay() && isWindowTrustedOverlay()) {
+            getPendingTransaction().setTrustedOverlay(mSurfaceControl, true);
+        }
+    }
+
+    void updateTrustedOverlay() {
+        mInputWindowHandle.setTrustedOverlay(getPendingTransaction(), mSurfaceControl,
+                isWindowTrustedOverlay());
+    }
+
+    boolean isWindowTrustedOverlay() {
         return InputMonitor.isTrustedOverlay(mAttrs.type)
                 || ((mAttrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0
                         && mSession.mCanAddInternalSystemWindow)
@@ -2755,12 +2769,7 @@
                 // bounds, as they would be used to display the dim layer.
                 final TaskFragment taskFragment = getTaskFragment();
                 if (taskFragment != null) {
-                    final Task task = taskFragment.asTask();
-                    if (task != null) {
-                        task.getDimBounds(mTmpRect);
-                    } else {
-                        mTmpRect.set(taskFragment.getBounds());
-                    }
+                    taskFragment.getDimBounds(mTmpRect);
                 } else if (getRootTask() != null) {
                     getRootTask().getDimBounds(mTmpRect);
                 }
@@ -3732,30 +3741,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();
     }
@@ -5190,9 +5213,6 @@
             updateFrameRateSelectionPriorityIfNeeded();
             updateScaleIfNeeded();
             mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
-            if (surfaceTrustedOverlay()) {
-                getSyncTransaction().setTrustedOverlay(mSurfaceControl, isWindowTrustedOverlay());
-            }
         }
         super.prepareSurfaces();
     }
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 43e47d7..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;
@@ -872,17 +872,9 @@
             "enable_permission_based_access";
     private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false;
 
-    private static final String ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG =
-            "enable_device_policy_engine";
-    private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = true;
-
     // TODO(b/265683382) remove the flag after rollout.
     public static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = false;
 
-    // TODO(b/261999445) remove the flag after rollout.
-    private static final String HEADLESS_FLAG = "headless";
-    private static final boolean DEFAULT_HEADLESS_FLAG = true;
-
     // TODO(b/266831522) remove the flag after rollout.
     private static final String APPLICATION_EXEMPTIONS_FLAG = "application_exemptions";
     private static final boolean DEFAULT_APPLICATION_EXEMPTIONS_FLAG = true;
@@ -4025,75 +4017,41 @@
     }
 
     private void clearDeviceOwnerUserRestriction(UserHandle userHandle) {
-        if (isHeadlessFlagEnabled()) {
-            for (int userId : mUserManagerInternal.getUserIds()) {
-                UserHandle user = UserHandle.of(userId);
-                // ManagedProvisioning/DPC sets DISALLOW_ADD_USER. Clear to recover to the
-                // original state
-                if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, user)) {
-                    mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER,
-                            false, user);
-                }
-                // When a device owner is set, the system automatically restricts adding a
-                // managed profile.
-                // Remove this restriction when the device owner is cleared.
-                if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE,
-                        user)) {
-                    mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE,
-                            false,
-                            user);
-                }
-                // When a device owner is set, the system automatically restricts adding a
-                // clone profile.
-                // Remove this restriction when the device owner is cleared.
-                if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, user)) {
-                    mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE,
-                            false, user);
-                }
-
-                // When a device owner is set, the system automatically restricts adding a
-                // private profile.
-                // Remove this restriction when the device owner is cleared.
-                if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
-                        user)) {
-                    mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
-                            false, user);
-                }
-            }
-        } else {
-            // ManagedProvisioning/DPC sets DISALLOW_ADD_USER. Clear to recover to the original state
-            if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, userHandle)) {
-                mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false,
-                        userHandle);
+        for (int userId : mUserManagerInternal.getUserIds()) {
+            UserHandle user = UserHandle.of(userId);
+            // ManagedProvisioning/DPC sets DISALLOW_ADD_USER. Clear to recover to the
+            // original state
+            if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, user)) {
+                mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER,
+                        false, user);
             }
             // When a device owner is set, the system automatically restricts adding a
             // managed profile.
             // Remove this restriction when the device owner is cleared.
             if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE,
-                    userHandle)) {
+                    user)) {
                 mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE,
                         false,
-                        userHandle);
+                        user);
             }
-            // When a device owner is set, the system automatically restricts adding a clone
-            // profile.
+            // When a device owner is set, the system automatically restricts adding a
+            // clone profile.
             // Remove this restriction when the device owner is cleared.
-            if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE,
-                    userHandle)) {
+            if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, user)) {
                 mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE,
-                        false,
-                        userHandle);
+                        false, user);
             }
 
             // When a device owner is set, the system automatically restricts adding a
             // private profile.
             // Remove this restriction when the device owner is cleared.
             if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
-                    userHandle)) {
+                    user)) {
                 mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
-                        false, userHandle);
+                        false, user);
             }
         }
+
     }
 
     /**
@@ -6476,7 +6434,7 @@
                          KeyChain.bindAsUser(mContext, userHandle)) {
                 IKeyChainService keyChain = keyChainConnection.getService();
                 return keyChain.setGrant(granteeUid, alias, hasGrant);
-            } catch (RemoteException e) {
+            } catch (RemoteException | AssertionError e) {
                 Slogf.e(LOG_TAG, "Setting grant for package.", e);
                 return false;
             }
@@ -7956,14 +7914,8 @@
                 hasCallingOrSelfPermission(permission.TRIGGER_LOST_MODE));
 
         synchronized (getLockObject()) {
-            // TODO(b/261999445): Remove
-            ActiveAdmin admin;
-            if (isHeadlessFlagEnabled()) {
-                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-            } else {
-                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
-                        UserHandle.USER_SYSTEM);
-            }
+            ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
+
             Preconditions.checkState(admin != null,
                     "Lost mode location updates can only be sent on an organization-owned device.");
             mInjector.binderWithCleanCallingIdentity(() -> {
@@ -9449,39 +9401,24 @@
                 // profile, such that the admin on that managed profile has extended management
                 // capabilities that can affect the entire device (but not access private data
                 // on the primary profile).
-                if (isHeadlessFlagEnabled()) {
-                    for (int u : mUserManagerInternal.getUserIds()) {
-                        mUserManager.setUserRestriction(
-                                UserManager.DISALLOW_ADD_MANAGED_PROFILE, true,
-                                UserHandle.of(u));
-                        // Restrict adding a clone profile when a device owner is set on the device.
-                        // That is to prevent the co-existence of a clone profile and a device owner
-                        // on the same device.
-                        // CDD for reference : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support
-                        mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE,
-                                true,
-                                UserHandle.of(u));
-
-                        // Restrict adding a private profile when a device owner is set.
-                        mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
-                                true,
-                                UserHandle.of(u));
-                    }
-                } else {
-                    mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE,
-                            true,
-                            UserHandle.of(userId));
+                for (int u : mUserManagerInternal.getUserIds()) {
+                    mUserManager.setUserRestriction(
+                            UserManager.DISALLOW_ADD_MANAGED_PROFILE, true,
+                            UserHandle.of(u));
                     // Restrict adding a clone profile when a device owner is set on the device.
                     // That is to prevent the co-existence of a clone profile and a device owner
                     // on the same device.
                     // CDD for reference : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support
                     mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE,
                             true,
-                            UserHandle.of(userId));
+                            UserHandle.of(u));
+
+                    // Restrict adding a private profile when a device owner is set.
                     mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
                             true,
-                            UserHandle.of(userId));
+                            UserHandle.of(u));
                 }
+
                 // TODO Send to system too?
                 sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId);
             });
@@ -20119,14 +20056,8 @@
         synchronized (getLockObject()) {
             // Only DO or COPE PO can turn on CC mode, so take a shortcut here and only look at
             // their ActiveAdmin, instead of iterating through all admins.
-            ActiveAdmin admin;
-            // TODO(b/261999445): remove
-            if (isHeadlessFlagEnabled()) {
-                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-            } else {
-                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
-                        UserHandle.USER_SYSTEM);
-            }
+            ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
+
             return admin != null ? admin.mCommonCriteriaMode : false;
         }
     }
@@ -21393,7 +21324,7 @@
     }
 
     private void disallowAddUser() {
-        if (!isHeadlessFlagEnabled() || mIsAutomotive) {
+        if (mIsAutomotive) {
             // Auto still enables adding users due to the communal nature of those devices
             if (mInjector.userManagerIsHeadlessSystemUserMode()) {
                 Slogf.i(LOG_TAG, "Not setting DISALLOW_ADD_USER on headless system user mode.");
@@ -21711,14 +21642,7 @@
     }
 
     private boolean isUsbDataSignalingEnabledInternalLocked() {
-        // TODO(b/261999445): remove
-        ActiveAdmin admin;
-        if (isHeadlessFlagEnabled()) {
-            admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-        } else {
-            admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
-                    UserHandle.USER_SYSTEM);
-        }
+        ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
         return admin == null || admin.mUsbDataSignalingEnabled;
     }
 
@@ -21785,14 +21709,7 @@
     @Override
     public int getMinimumRequiredWifiSecurityLevel() {
         synchronized (getLockObject()) {
-            ActiveAdmin admin;
-            // TODO(b/261999445): remove
-            if (isHeadlessFlagEnabled()) {
-                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-            } else {
-                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
-                        UserHandle.USER_SYSTEM);
-            }
+            ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
             return (admin == null) ? DevicePolicyManager.WIFI_SECURITY_OPEN
                     : admin.mWifiMinimumSecurityLevel;
         }
@@ -23169,16 +23086,8 @@
                             || isProfileOwnerOfOrganizationOwnedDevice(caller));
         }
         synchronized (getLockObject()) {
-            // TODO(b/261999445): Remove
-            ActiveAdmin admin;
-            if (isHeadlessFlagEnabled()) {
-                admin =
+            ActiveAdmin admin =
                         getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-            } else {
-                admin =
-                        getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
-                                UserHandle.USER_SYSTEM);
-            }
 
             if (admin != null) {
                 final String memtagProperty = "arm64.memtag.bootctl";
@@ -23211,29 +23120,14 @@
                             || isSystemUid(caller));
         }
         synchronized (getLockObject()) {
-            // TODO(b/261999445): Remove
-            ActiveAdmin admin;
-            if (isHeadlessFlagEnabled()) {
-                admin =
+            ActiveAdmin admin =
                         getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-            } else {
-                admin =
-                        getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
-                                UserHandle.USER_SYSTEM);
-            }
             return admin != null
                     ? admin.mtePolicy
                     : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
         }
     }
 
-    private boolean isHeadlessFlagEnabled() {
-        return DeviceConfig.getBoolean(
-                NAMESPACE_DEVICE_POLICY_MANAGER,
-                HEADLESS_FLAG,
-                DEFAULT_HEADLESS_FLAG);
-    }
-
     @Override
     public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() {
         synchronized (getLockObject()) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
index 16876ac..eb893fc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
@@ -251,7 +251,14 @@
 
     private int runSetDeviceOwner(PrintWriter pw) {
         parseArgs();
-        mService.setActiveAdmin(mComponent, /* refreshing= */ true, mUserId);
+        boolean isAdminAdded = false;
+        try {
+            mService.setActiveAdmin(mComponent, /* refreshing= */ false, mUserId);
+            isAdminAdded = true;
+        } catch (IllegalArgumentException e) {
+            pw.printf("%s was already an admin for user %d. No need to set it again.\n",
+                    mComponent.flattenToShortString(), mUserId);
+        }
 
         try {
             if (!mService.setDeviceOwner(mComponent, mUserId,
@@ -260,8 +267,10 @@
                         "Can't set package " + mComponent + " as device owner.");
             }
         } catch (Exception e) {
-            // Need to remove the admin that we just added.
-            mService.removeActiveAdmin(mComponent, UserHandle.USER_SYSTEM);
+            if (isAdminAdded) {
+                // Need to remove the admin that we just added.
+                mService.removeActiveAdmin(mComponent, mUserId);
+            }
             throw e;
         }
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 49ad84a..c26aee8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -51,6 +51,7 @@
 import android.database.sqlite.SQLiteCompatibilityWalFlags;
 import android.database.sqlite.SQLiteGlobal;
 import android.graphics.GraphicsStatsService;
+import android.graphics.Typeface;
 import android.hardware.display.DisplayManagerInternal;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityModuleConnector;
@@ -160,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;
@@ -916,6 +918,14 @@
             SystemServerInitThreadPool tp = SystemServerInitThreadPool.start();
             mDumper.addDumpable(tp);
 
+            // Lazily load the pre-installed system font map in SystemServer only if we're not doing
+            // the optimized font loading in the FontManagerService.
+            if (!com.android.text.flags.Flags.useOptimizedBoottimeFontLoading()
+                    && Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
+                Slog.i(TAG, "Loading pre-installed system font map.");
+                Typeface.loadPreinstalledSystemFontMap();
+            }
+
             // Attach JVMTI agent if this is a debuggable build and the system property is set.
             if (Build.IS_DEBUGGABLE) {
                 // Property is of the form "library_path=parameters".
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 240585c..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
             }
         }
     }
@@ -85,10 +85,10 @@
         appId: Int,
         userId: Int
     ) {
-        resetPermissionStates(packageName, userId)
+        resetRuntimePermissions(packageName, userId)
     }
 
-    private fun MutateStateScope.resetPermissionStates(packageName: String, userId: Int) {
+    fun MutateStateScope.resetRuntimePermissions(packageName: String, userId: Int) {
         // It's okay to skip resetting permissions for packages that are removed,
         // because their states will be trimmed in onPackageRemoved()/onAppIdRemoved()
         val packageState = newState.externalState.packageStates[packageName] ?: return
@@ -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 bb24d51..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,9 +1907,8 @@
 
     override fun resetRuntimePermissions(androidPackage: AndroidPackage, userId: Int) {
         service.mutateState {
-            with(policy) {
-                resetRuntimePermissions(androidPackage.packageName, userId)
-            }
+            with(policy) { resetRuntimePermissions(androidPackage.packageName, userId) }
+            with(devicePolicy) { resetRuntimePermissions(androidPackage.packageName, userId) }
         }
     }
 
@@ -1745,9 +1916,8 @@
         packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
             service.mutateState {
                 snapshot.packageStates.forEach { (_, packageState) ->
-                    with(policy) {
-                        resetRuntimePermissions(packageState.packageName, userId)
-                    }
+                    with(policy) { resetRuntimePermissions(packageState.packageName, userId) }
+                    with(devicePolicy) { resetRuntimePermissions(packageState.packageName, userId) }
                 }
             }
         }
@@ -1755,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)
@@ -1780,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()
         }
@@ -1808,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
                     }
                 }
@@ -1822,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
                 }
@@ -1846,7 +2019,8 @@
             isDelayedPermissionBackupFinished -= userId
         }
         permissionControllerManager.stageAndApplyRuntimePermissionsBackup(
-            backup, UserHandle.of(userId)
+            backup,
+            UserHandle.of(userId)
         )
     }
 
@@ -1860,7 +2034,9 @@
             }
         }
         permissionControllerManager.applyStagedRuntimePermissionBackup(
-            packageName, UserHandle.of(userId), PermissionThread.getExecutor()
+            packageName,
+            UserHandle.of(userId),
+            PermissionThread.getExecutor()
         ) { hasMoreBackup ->
             if (hasMoreBackup) {
                 return@applyStagedRuntimePermissionBackup
@@ -1907,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)
             }
@@ -1929,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.
@@ -1960,10 +2131,7 @@
         println("Permission groups:")
         withIndent {
             state.systemState.permissionGroups.forEachIndexed { _, _, permissionGroup ->
-                println(
-                    "${permissionGroup.name}: " +
-                        "packageName=${permissionGroup.packageName}"
-                )
+                println("${permissionGroup.name}: " + "packageName=${permissionGroup.packageName}")
             }
         }
 
@@ -1992,7 +2160,9 @@
                     println("Permissions:")
                     withIndent {
                         userState.appIdPermissionFlags[appId]?.forEachIndexed {
-                            _, permissionName, flags ->
+                            _,
+                            permissionName,
+                            flags ->
                             val isGranted = PermissionFlags.isPermissionGranted(flags)
                             println(
                                 "$permissionName: granted=$isGranted, flags=" +
@@ -2002,7 +2172,9 @@
                     }
 
                     userState.appIdDevicePermissionFlags[appId]?.forEachIndexed {
-                            _, deviceId, devicePermissionFlags ->
+                        _,
+                        deviceId,
+                        devicePermissionFlags ->
                         println("Permissions (Device $deviceId):")
                         withIndent {
                             devicePermissionFlags.forEachIndexed { _, permissionName, flags ->
@@ -2017,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)}")
                         }
                     }
@@ -2029,7 +2202,9 @@
                             println("App ops:")
                             withIndent {
                                 userState.packageAppOpModes[packageName]?.forEachIndexed {
-                                        _, appOpName, appOpMode ->
+                                    _,
+                                    appOpName,
+                                    appOpMode ->
                                     val modeName = AppOpsManager.modeToName(appOpMode)
                                     println("$appOpName: mode=$modeName")
                                 }
@@ -2048,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.
@@ -2086,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 {
@@ -2097,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)
                 }
             }
@@ -2131,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) {
@@ -2170,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
@@ -2212,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)
         }
     }
@@ -2241,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) {
@@ -2262,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()
@@ -2303,9 +2478,7 @@
         }
     }
 
-    /**
-     * @see PackageManagerLocal.withFilteredSnapshot
-     */
+    /** @see PackageManagerLocal.withFilteredSnapshot */
     private fun PackageManagerLocal.withFilteredSnapshot(
         callingUid: Int,
         userId: Int
@@ -2323,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
@@ -2374,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)
@@ -2397,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) {
@@ -2424,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) {
@@ -2443,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
@@ -2476,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)
 
@@ -2512,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
                         }
@@ -2541,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")
@@ -2614,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/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
index 55fde00..e71ea26 100644
--- a/services/tests/displayservicetests/AndroidManifest.xml
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -28,6 +28,7 @@
     <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.MANAGE_USB" />
 
     <!-- Permissions needed for DisplayTransformManagerTest -->
     <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
index 4b124ca..8cc3408 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -100,6 +100,14 @@
     }
 
     @Test
+    public void testPowerThrottlingMapId() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(5);
+
+        assertEquals("concurrent1", configLayout.getAt(0).getPowerThrottlingMapId());
+        assertEquals("concurrent2", configLayout.getAt(1).getPowerThrottlingMapId());
+    }
+
+    @Test
     public void testRearDisplayLayout() {
         Layout configLayout = mDeviceStateToLayoutMap.get(2);
 
@@ -133,13 +141,15 @@
                 mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT,
                 /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness1",
                 /* refreshRateZoneId= */ "zone1",
-                /* refreshRateThermalThrottlingMapId= */ "rr1");
+                /* refreshRateThermalThrottlingMapId= */ "rr1",
+                /* powerThrottlingMapId= */ "power1");
         testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L),
                 /* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1",
                 mDisplayIdProducerMock, Layout.Display.POSITION_REAR,
                 /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness2",
                 /* refreshRateZoneId= */ "zone2",
-                /* refreshRateThermalThrottlingMapId= */ "rr2");
+                /* refreshRateThermalThrottlingMapId= */ "rr2",
+                /* powerThrottlingMapId= */ "power2");
         testLayout.postProcessLocked();
 
         assertEquals(testLayout, configLayout);
@@ -200,7 +210,8 @@
                     mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT,
                     DisplayAddress.fromPhysicalDisplayId(123L),
                     /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
-                    /* refreshRateThermalThrottlingMapId= */ null));
+                    /* refreshRateThermalThrottlingMapId= */ null,
+                    /* powerThrottlingMapId= */ null));
     }
 
     @Test
@@ -215,7 +226,8 @@
                     mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT,
                     DisplayAddress.fromPhysicalDisplayId(987L),
                     /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
-                    /* refreshRateThermalThrottlingMapId= */ null));
+                    /* refreshRateThermalThrottlingMapId= */ null,
+                    /* powerThrottlingMapId= */ null));
     }
 
     @Test
@@ -271,7 +283,8 @@
                 enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
                 leadDisplayAddress, /* brightnessThrottlingMapId= */ null,
                 /* refreshRateZoneId= */ null,
-                /* refreshRateThermalThrottlingMapId= */ null);
+                /* refreshRateThermalThrottlingMapId= */ null,
+                /* powerThrottlingMapId= */ null);
     }
 
     private void setupDeviceStateToLayoutMap() throws IOException {
@@ -327,7 +340,6 @@
                 +        "<brightnessThrottlingMapId>concurrent2</brightnessThrottlingMapId>\n"
                 +      "</display>\n"
                 +    "</layout>\n"
-
                 +    "<layout>\n"
                 +      "<state>3</state> \n"
                 +      "<display enabled=\"true\" defaultDisplay=\"true\" "
@@ -338,7 +350,6 @@
                 +        "<address>678</address>\n"
                 +      "</display>\n"
                 +    "</layout>\n"
-
                 +    "<layout>\n"
                 +      "<state>4</state> \n"
                 +      "<display enabled=\"true\" defaultDisplay=\"true\" >\n"
@@ -352,6 +363,20 @@
                 +      "</display>\n"
                 +    "</layout>\n"
                 +    "<layout>\n"
+                +      "<state>5</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+                +        "<address>345</address>\n"
+                +        "<position>front</position>\n"
+                +        "<powerThrottlingMapId>concurrent1</powerThrottlingMapId>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"true\">\n"
+                +        "<address>678</address>\n"
+                +        "<position>rear</position>\n"
+                +        "<powerThrottlingMapId>concurrent2</powerThrottlingMapId>\n"
+                +      "</display>\n"
+                +    "</layout>\n"
+
+                +    "<layout>\n"
                 +      "<state>99</state> \n"
                 +      "<display enabled=\"true\" defaultDisplay=\"true\" "
                 +                                          "refreshRateZoneId=\"zone1\">\n"
@@ -361,6 +386,7 @@
                 +         "<refreshRateThermalThrottlingMapId>"
                 +           "rr1"
                 +         "</refreshRateThermalThrottlingMapId>"
+                +         "<powerThrottlingMapId>power1</powerThrottlingMapId>\n"
                 +       "</display>\n"
                 +       "<display enabled=\"false\" displayGroup=\"group1\" "
                 +                                           "refreshRateZoneId=\"zone2\">\n"
@@ -370,6 +396,7 @@
                 +         "<refreshRateThermalThrottlingMapId>"
                 +           "rr2"
                 +         "</refreshRateThermalThrottlingMapId>"
+                +         "<powerThrottlingMapId>power2</powerThrottlingMapId>\n"
                 +       "</display>\n"
                 +     "</layout>\n"
                 +   "</layouts>\n";
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 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..32e2871 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -33,6 +33,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -60,6 +61,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 +115,8 @@
     @Mock
     private LogicalLight mMockedBacklight;
     @Mock
+    private DisplayNotificationManager mMockedDisplayNotificationManager;
+    @Mock
     private DisplayManagerFlags mFlags;
 
     private Handler mHandler;
@@ -148,7 +152,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();
 
@@ -696,7 +700,7 @@
 
         // Turn off.
         Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_OFF, 0,
-                0);
+                0, null);
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
         assertThat(mListener.changedDisplays.size()).isEqualTo(1);
         mListener.changedDisplays.clear();
@@ -1000,7 +1004,7 @@
         // Turn on / initialize
         assumeTrue(displayDevice.getDisplayDeviceConfig().hasSdrToHdrRatioSpline());
         Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
-                0);
+                0, null);
         changeStateRunnable.run();
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
         mListener.changedDisplays.clear();
@@ -1009,7 +1013,7 @@
 
         // HDR time!
         Runnable goHdrRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 1f,
-                0);
+                0, null);
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
         // Display state didn't change, no listeners should have happened
         assertThat(mListener.changedDisplays.size()).isEqualTo(0);
@@ -1040,7 +1044,7 @@
 
         // Turn on / initialize
         Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
-                0);
+                0, null);
         changeStateRunnable.run();
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
         mListener.changedDisplays.clear();
@@ -1067,7 +1071,7 @@
 
         // Turn on / initialize
         Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
-                0);
+                0, null);
         changeStateRunnable.run();
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
         mListener.changedDisplays.clear();
@@ -1092,7 +1096,7 @@
 
         // Turn on / initialize
         Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
-                0);
+                0, null);
         changeStateRunnable.run();
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
         mListener.changedDisplays.clear();
@@ -1115,7 +1119,7 @@
 
         // Turn on / initialize
         Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
-                0);
+                0, null);
         changeStateRunnable.run();
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
         mListener.changedDisplays.clear();
@@ -1142,9 +1146,9 @@
             Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(
                     supportedState, 0, 0, mDisplayOffloadSession);
             changeStateRunnable.run();
-
-            verify(mDisplayOffloader).startOffload();
         }
+
+        verify(mDisplayOffloader, times(mDisplayOffloadSupportedStates.size())).startOffload();
     }
 
     @Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 065dd1f..8b13018 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -720,13 +720,15 @@
                 mIdProducer, POSITION_UNKNOWN,
                 /* leadDisplayAddress= */ null,
                 /* brightnessThrottlingMapId= */ "concurrent",
-                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null,
+                /* powerThrottlingMapId= */ "concurrent");
         layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
                 /* isDefault= */ false, /* isEnabled= */ true, /* displayGroup= */ null,
                 mIdProducer, POSITION_UNKNOWN,
                 /* leadDisplayAddress= */ null,
                 /* brightnessThrottlingMapId= */ "concurrent",
-                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null,
+                /* powerThrottlingMapId= */ "concurrent");
         when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
 
         layout = new Layout();
@@ -927,7 +929,7 @@
                 /* isDefault= */ false, /* isEnabled= */ true, /* displayGroupName= */ null,
                 mIdProducer, POSITION_REAR, /* leadDisplayAddress= */ null,
                 /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
-                /* refreshRateThermalThrottlingMapId= */null);
+                /* refreshRateThermalThrottlingMapId= */null, /* powerThrottlingMapId= */null);
         when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
 
         when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
@@ -986,7 +988,7 @@
         layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer,
                 Layout.Display.POSITION_UNKNOWN, /* leadDisplayAddress= */ null,
                 /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
-                /* refreshRateThermalThrottlingMapId= */ null);
+                /* refreshRateThermalThrottlingMapId= */ null, /* powerThrottlingMapId= */ null);
     }
 
     private void advanceTime(long timeMs) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java
new file mode 100644
index 0000000..5c50acb
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static com.android.internal.display.RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import android.hardware.display.DisplayManager;
+import android.testing.TestableContext;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.display.RefreshRateSettingsUtils;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RefreshRateSettingsUtilsTest {
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
+    @Mock
+    private DisplayManager mDisplayManagerMock;
+    @Mock
+    private Display mDisplayMock;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext.addMockSystemService(DisplayManager.class, mDisplayManagerMock);
+
+        Display.Mode[] modes = new Display.Mode[]{
+                new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
+                        /* refreshRate= */ 60),
+                new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
+                        /* refreshRate= */ 120),
+                new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
+                        /* refreshRate= */ 90)
+        };
+
+        when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock);
+        when(mDisplayMock.getSupportedModes()).thenReturn(modes);
+    }
+
+    @Test
+    public void testFindHighestRefreshRateForDefaultDisplay() {
+        when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(null);
+        assertEquals(DEFAULT_REFRESH_RATE,
+                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
+                /* delta= */ 0);
+
+        when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock);
+        assertEquals(120,
+                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
+                /* delta= */ 0);
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index 22d2622..c0e0df9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -35,6 +35,7 @@
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.testutils.TestHandler;
 
 import org.junit.Before;
@@ -63,12 +64,13 @@
     @Mock
     private BrightnessClamper<BrightnessClamperController.DisplayDeviceData> mMockClamper;
     @Mock
+    private DisplayManagerFlags mFlags;
+    @Mock
     private BrightnessModifier mMockModifier;
     @Mock
     private DisplayManagerInternal.DisplayPowerRequest mMockRequest;
     @Mock
     private DeviceConfig.Properties mMockProperties;
-
     private BrightnessClamperController mClamperController;
     private TestInjector mTestInjector;
 
@@ -219,7 +221,7 @@
 
     private BrightnessClamperController createBrightnessClamperController() {
         return new BrightnessClamperController(mTestInjector, mTestHandler, mMockExternalListener,
-                mMockDisplayDeviceData, mMockContext);
+                mMockDisplayDeviceData, mMockContext, mFlags);
     }
 
     private class TestInjector extends BrightnessClamperController.Injector {
@@ -247,7 +249,8 @@
         List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>> getClampers(
                 Handler handler,
                 BrightnessClamperController.ClamperChangeListener clamperChangeListener,
-                BrightnessClamperController.DisplayDeviceData data) {
+                BrightnessClamperController.DisplayDeviceData data,
+                DisplayManagerFlags flags) {
             mCapturedChangeListener = clamperChangeListener;
             return mClampers;
         }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java
new file mode 100644
index 0000000..b3f33ad
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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.brightness.clamper;
+
+import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.Temperature;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.testutils.FakeDeviceConfigInterface;
+import com.android.server.testutils.TestHandler;
+
+import junitparams.JUnitParamsRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@RunWith(JUnitParamsRunner.class)
+public class BrightnessPowerClamperTest {
+    private static final String TAG = "BrightnessPowerClamperTest";
+    private static final float FLOAT_TOLERANCE = 0.001f;
+
+    private static final String DISPLAY_ID = "displayId";
+    @Mock
+    private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
+    private TestPmicMonitor mPmicMonitor;
+    private final FakeDeviceConfigInterface mFakeDeviceConfigInterface =
+            new FakeDeviceConfigInterface();
+    private final TestHandler mTestHandler = new TestHandler(null);
+    private BrightnessPowerClamper mClamper;
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mClamper = new BrightnessPowerClamper(new TestInjector(), mTestHandler,
+                mMockClamperChangeListener, new TestPowerData());
+        mTestHandler.flush();
+    }
+
+    @Test
+    public void testTypeIsPower() {
+        assertEquals(BrightnessClamper.Type.POWER, mClamper.getType());
+    }
+
+    @Test
+    public void testNoThrottlingData() {
+        assertFalse(mClamper.isActive());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testPowerThrottlingNoOngoingAnimation() throws RemoteException {
+        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE);
+        mTestHandler.flush();
+        assertFalse(mClamper.isActive());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+        // update a new device config for power-throttling.
+        mClamper.onDisplayChanged(new TestPowerData(
+                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 100f))));
+
+        mPmicMonitor.setAvgPowerConsumed(200f);
+        float expectedBrightness = 0.5f;
+        expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX;
+
+        mTestHandler.flush();
+        // Assume current brightness as max, as there is no throttling.
+        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_CRITICAL);
+        // update a new device config for power-throttling.
+        mClamper.onDisplayChanged(new TestPowerData(
+                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 50f))));
+
+        mPmicMonitor.setAvgPowerConsumed(100f);
+        expectedBrightness = 0.5f * PowerManager.BRIGHTNESS_MAX;
+        mTestHandler.flush();
+        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testPowerThrottlingWithOngoingAnimation() throws RemoteException {
+        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE);
+        mTestHandler.flush();
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+        // update a new device config for power-throttling.
+        mClamper.onDisplayChanged(new TestPowerData(
+                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 100f))));
+
+        mPmicMonitor.setAvgPowerConsumed(200f);
+        float expectedBrightness = 0.5f;
+        expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX;
+
+        mTestHandler.flush();
+        // Assume current brightness as max, as there is no throttling.
+        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_CRITICAL);
+        // update a new device config for power-throttling.
+        mClamper.onDisplayChanged(new TestPowerData(
+                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 50f))));
+
+        mPmicMonitor.setAvgPowerConsumed(100f);
+        expectedBrightness = 0.5f * PowerManager.BRIGHTNESS_MAX;
+        mTestHandler.flush();
+        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testPowerThrottlingRemoveBrightnessCap() throws RemoteException {
+        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT);
+        mTestHandler.flush();
+        assertFalse(mClamper.isActive());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+        // update a new device config for power-throttling.
+        mClamper.onDisplayChanged(new TestPowerData(
+                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, 100f))));
+
+        mPmicMonitor.setAvgPowerConsumed(200f);
+        float expectedBrightness = 0.5f;
+        expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX;
+
+        mTestHandler.flush();
+
+        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_NONE);
+
+        mPmicMonitor.setAvgPowerConsumed(100f);
+        // No cap applied for Temperature.THROTTLING_NONE
+        expectedBrightness = PowerManager.BRIGHTNESS_MAX;
+        mTestHandler.flush();
+
+        // clamper should not be active anymore.
+        assertFalse(mClamper.isActive());
+        // Assume current brightness as max, as there is no throttling.
+        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+    }
+
+
+    private static class TestPmicMonitor extends PmicMonitor {
+        private Temperature mCurrentTemperature;
+        private final PowerChangeListener mListener;
+        TestPmicMonitor(PowerChangeListener listener, int pollingTime) {
+            super(listener, pollingTime);
+            mListener = listener;
+        }
+        public void setAvgPowerConsumed(float power) {
+            int status = mCurrentTemperature.getStatus();
+            mListener.onChanged(power, status);
+        }
+        public void setThermalStatus(@Temperature.ThrottlingStatus int status) {
+            mCurrentTemperature = new Temperature(100, Temperature.TYPE_SKIN, "test_temp", status);
+        }
+    }
+
+    private class TestInjector extends BrightnessPowerClamper.Injector {
+        @Override
+        TestPmicMonitor getPmicMonitor(PowerChangeListener listener,
+                int pollingTime) {
+            mPmicMonitor = new TestPmicMonitor(listener, pollingTime);
+            return mPmicMonitor;
+        }
+
+        @Override
+        DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+            return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface);
+        }
+    }
+
+    private static class TestPowerData implements BrightnessPowerClamper.PowerData {
+
+        private final String mUniqueDisplayId;
+        private final String mDataId;
+        private final PowerThrottlingData mData;
+        private final PowerThrottlingConfigData mConfigData;
+
+        private TestPowerData() {
+            this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null);
+        }
+
+        private TestPowerData(List<ThrottlingLevel> data) {
+            this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data);
+        }
+
+        private TestPowerData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data) {
+            mUniqueDisplayId = uniqueDisplayId;
+            mDataId = dataId;
+            mData = PowerThrottlingData.create(data);
+            mConfigData = new PowerThrottlingConfigData(0.1f, 10);
+        }
+
+        @NonNull
+        @Override
+        public String getUniqueDisplayId() {
+            return mUniqueDisplayId;
+        }
+
+        @NonNull
+        @Override
+        public String getPowerThrottlingDataId() {
+            return mDataId;
+        }
+
+        @Nullable
+        @Override
+        public PowerThrottlingData getPowerThrottlingData() {
+            return mData;
+        }
+
+        @Nullable
+        @Override
+        public PowerThrottlingConfigData getPowerThrottlingConfigData() {
+            return mConfigData;
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index c63fac9..ee187ba 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -22,6 +22,9 @@
 
 import static org.mockito.ArgumentMatchers.any;
 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.when;
 
@@ -97,6 +100,37 @@
     }
 
     @Test
+    public void testRegisterHdrListener() {
+        verify(mMockHdrInfoListener).register(mMockBinder);
+    }
+
+    @Test
+    public void testRegisterOtherHdrListenerWhenCalledWithOtherToken() {
+        IBinder otherBinder = mock(IBinder.class);
+        mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT, MIN_HDR_PERCENT, otherBinder);
+
+        verify(mMockHdrInfoListener).unregister(mMockBinder);
+        verify(mMockHdrInfoListener).register(otherBinder);
+    }
+
+    @Test
+    public void testRegisterHdrListenerOnceWhenCalledWithSameToken() {
+        mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT, MIN_HDR_PERCENT, mMockBinder);
+
+        verify(mMockHdrInfoListener, never()).unregister(mMockBinder);
+        verify(mMockHdrInfoListener, times(1)).register(mMockBinder);
+    }
+
+    @Test
+    public void testRegisterNotCalledIfHbmConfigIsMissing() {
+        IBinder otherBinder = mock(IBinder.class);
+        mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT, -1, otherBinder);
+
+        verify(mMockHdrInfoListener).unregister(mMockBinder);
+        verify(mMockHdrInfoListener, never()).register(otherBinder);
+    }
+
+    @Test
     public void testClamper_AmbientLuxChangesAboveLimit() {
         mHdrClamper.onAmbientLuxChange(500);
 
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/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
index c280349..79222c0 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
@@ -23,6 +23,7 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -37,6 +38,7 @@
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.R;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -54,6 +56,8 @@
     private Resources mMockedResources;
     @Mock
     private DisplayManagerInternal mDisplayManagerInternal;
+    @Mock
+    private DisplayManagerFlags mDisplayManagerFlagsMock;
 
     private MockitoSession mSession;
     private Resources mResources;
@@ -63,10 +67,6 @@
     @Before
     public void setUp() {
         DisplayManagerInternal displayManagerInternal = mock(DisplayManagerInternal.class);
-        mDisplayWhiteBalanceTintController =
-                new DisplayWhiteBalanceTintController(displayManagerInternal);
-        mDisplayWhiteBalanceTintController.setUp(InstrumentationRegistry.getContext(), true);
-        mDisplayWhiteBalanceTintController.setActivated(true);
 
         mSession = ExtendedMockito.mockitoSession()
                 .initMocks(this)
@@ -74,6 +74,13 @@
                 .strictness(Strictness.LENIENT)
                 .startMocking();
 
+        mDisplayWhiteBalanceTintController =
+                new DisplayWhiteBalanceTintController(displayManagerInternal,
+                        mDisplayManagerFlagsMock);
+        mDisplayWhiteBalanceTintController.setUp(InstrumentationRegistry.getContext(), true);
+        mDisplayWhiteBalanceTintController.setActivated(true);
+
+
         mResources = InstrumentationRegistry.getContext().getResources();
         // These Resources are common to all tests.
         doReturn(4000)
@@ -360,9 +367,47 @@
                 1e-6f /* tolerance */);
     }
 
+    @Test
+    public void testDisplayWhiteBalance_TransitionTimes() {
+        when(mDisplayManagerFlagsMock.isAdaptiveTone2Enabled()).thenReturn(false);
+        setUpTransitionTimes();
+        setUpTintController();
+
+        assertEquals(30L,
+                mDisplayWhiteBalanceTintController.getTransitionDurationMilliseconds(true));
+        assertEquals(30L,
+                mDisplayWhiteBalanceTintController.getTransitionDurationMilliseconds(false));
+    }
+
+    @Test
+    public void testDisplayWhiteBalance_TransitionTimesDirectional() {
+        when(mDisplayManagerFlagsMock.isAdaptiveTone2Enabled()).thenReturn(true);
+        setUpTransitionTimes();
+        setUpTintController();
+
+        assertEquals(400L,
+                mDisplayWhiteBalanceTintController.getTransitionDurationMilliseconds(true));
+        assertEquals(5000L,
+                mDisplayWhiteBalanceTintController.getTransitionDurationMilliseconds(false));
+    }
+
+
+    private void setUpTransitionTimes() {
+        doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries))
+                .when(mMockedResources)
+                .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
+        when(mMockedResources.getInteger(
+                R.integer.config_displayWhiteBalanceTransitionTime)).thenReturn(30);
+        when(mMockedResources.getInteger(
+                R.integer.config_displayWhiteBalanceTransitionTimeIncrease)).thenReturn(400);
+        when(mMockedResources.getInteger(
+                R.integer.config_displayWhiteBalanceTransitionTimeDecrease)).thenReturn(5000);
+
+    }
+
     private void setUpTintController() {
         mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
-                mDisplayManagerInternal);
+                mDisplayManagerInternal, mDisplayManagerFlagsMock);
         mDisplayWhiteBalanceTintController.setUp(mMockedContext, true);
         mDisplayWhiteBalanceTintController.setActivated(true);
     }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index b8c18e07..c4f72b3 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -27,6 +27,7 @@
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.server.display.mode.Vote.INVALID_SIZE;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -85,10 +86,12 @@
 
 import com.android.internal.R;
 import com.android.internal.display.BrightnessSynchronizer;
+import com.android.internal.display.RefreshRateSettingsUtils;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
 import com.android.server.display.DisplayDeviceConfig;
 import com.android.server.display.TestUtils;
 import com.android.server.display.feature.DisplayManagerFlags;
@@ -106,7 +109,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
+import org.mockito.quality.Strictness;
 import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
@@ -252,9 +255,15 @@
     @Mock
     private DisplayManagerFlags mDisplayManagerFlags;
 
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule =
+            new ExtendedMockitoRule.Builder(this)
+                    .setStrictness(Strictness.LENIENT)
+                    .spyStatic(RefreshRateSettingsUtils.class)
+                    .build();
+
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
         mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
         when(mContext.getContentResolver()).thenReturn(resolver);
@@ -1470,6 +1479,94 @@
     }
 
     @Test
+    public void testPeakRefreshRate_FlagEnabled() {
+        when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+                .thenReturn(true);
+        float highestRefreshRate = 130;
+        doReturn(highestRefreshRate).when(() ->
+                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext));
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+        director.start(sensorManager);
+
+        setPeakRefreshRate(Float.POSITIVE_INFINITY);
+
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
+                highestRefreshRate);
+    }
+
+    @Test
+    public void testPeakRefreshRate_FlagDisabled() {
+        when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+                .thenReturn(false);
+        float peakRefreshRate = 130;
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+        director.start(sensorManager);
+
+        setPeakRefreshRate(peakRefreshRate);
+
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
+                peakRefreshRate);
+    }
+
+    @Test
+    public void testMinRefreshRate_FlagEnabled() {
+        when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+                .thenReturn(true);
+        float highestRefreshRate = 130;
+        doReturn(highestRefreshRate).when(() ->
+                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext));
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+        director.start(sensorManager);
+
+        setMinRefreshRate(Float.POSITIVE_INFINITY);
+
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ highestRefreshRate,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+    }
+
+    @Test
+    public void testMinRefreshRate_FlagDisabled() {
+        when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+                .thenReturn(false);
+        float minRefreshRate = 130;
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+        director.start(sensorManager);
+
+        setMinRefreshRate(minRefreshRate);
+
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ minRefreshRate,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+    }
+
+    @Test
     public void testSensorRegistration() {
         // First, configure brightness zones or DMD won't register for sensor data.
         final FakeDeviceConfig config = mInjector.getDeviceConfig();
@@ -3167,6 +3264,13 @@
         waitForIdleSync();
     }
 
+    private void setMinRefreshRate(float fps) {
+        Settings.System.putFloat(mContext.getContentResolver(), Settings.System.MIN_REFRESH_RATE,
+                fps);
+        mInjector.notifyMinRefreshRateChanged();
+        waitForIdleSync();
+    }
+
     private static SensorManager createMockSensorManager(Sensor... sensors) {
         SensorManager sensorManager = mock(SensorManager.class);
         when(sensorManager.getSensorList(anyInt())).then((invocation) -> {
@@ -3216,6 +3320,7 @@
         private final SensorManagerInternal mSensorManagerInternal;
 
         private ContentObserver mPeakRefreshRateObserver;
+        private ContentObserver mMinRefreshRateObserver;
 
         FakesInjector() {
             this(null, null, null);
@@ -3247,6 +3352,12 @@
         }
 
         @Override
+        public void registerMinRefreshRateObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            mMinRefreshRateObserver = observer;
+        }
+
+        @Override
         public void registerDisplayListener(DisplayListener listener, Handler handler) {}
 
         @Override
@@ -3318,5 +3429,12 @@
                         PEAK_REFRESH_RATE_URI);
             }
         }
+
+        void notifyMinRefreshRateChanged() {
+            if (mMinRefreshRateObserver != null) {
+                mMinRefreshRateObserver.dispatchChange(false /*selfChange*/,
+                        MIN_REFRESH_RATE_URI);
+            }
+        }
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java
new file mode 100644
index 0000000..d5a92cb
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED;
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_ENABLED;
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE;
+import static android.hardware.usb.DisplayPortAltModeInfo.LINK_TRAINING_STATUS_FAILURE;
+
+import static org.junit.Assume.assumeFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.usb.DisplayPortAltModeInfo;
+import android.hardware.usb.UsbManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.display.notifications.ConnectedDisplayUsbErrorsDetector.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.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link ConnectedDisplayUsbErrorsDetector}
+ * Run: atest ConnectedDisplayUsbErrorsDetectorTest
+ */
+@SmallTest
+@RunWith(TestParameterInjector.class)
+public class ConnectedDisplayUsbErrorsDetectorTest {
+    @Mock
+    private Injector mMockedInjector;
+    @Mock
+    private UsbManager mMockedUsbManager;
+    @Mock
+    private DisplayManagerFlags mMockedFlags;
+    @Mock
+    private ConnectedDisplayUsbErrorsDetector.Listener mMockedListener;
+
+    /** Setup tests. */
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testNoErrorTypes(
+            @TestParameter final boolean isUsbManagerAvailable,
+            @TestParameter final boolean isUsbErrorsNotificationEnabled) {
+        // This is tested in #testErrorOnUsbCableNotCapableDp and #testErrorOnDpLinkTrainingFailure
+        assumeFalse(isUsbManagerAvailable && isUsbErrorsNotificationEnabled);
+        var detector = createErrorsDetector(isUsbManagerAvailable, isUsbErrorsNotificationEnabled);
+        // None of these should trigger an error now.
+        detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnUsbCableNotCapableDp());
+        detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnDpLinkTrainingFailure());
+        verify(mMockedUsbManager, never()).registerDisplayPortAltModeInfoListener(any(), any());
+        verify(mMockedListener, never()).onCableNotCapableDisplayPort();
+        verify(mMockedListener, never()).onDisplayPortLinkTrainingFailure();
+    }
+
+    @Test
+    public void testErrorOnUsbCableNotCapableDp() {
+        var detector = createErrorsDetector(/*isUsbManagerAvailable=*/ true,
+                /*isUsbErrorsNotificationEnabled=*/ true);
+        detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnUsbCableNotCapableDp());
+        verify(mMockedUsbManager).registerDisplayPortAltModeInfoListener(any(), any());
+        verify(mMockedListener).onCableNotCapableDisplayPort();
+        verify(mMockedListener, never()).onDisplayPortLinkTrainingFailure();
+    }
+
+    @Test
+    public void testErrorOnDpLinkTrainingFailure() {
+        var detector = createErrorsDetector(/*isUsbManagerAvailable=*/ true,
+                /*isUsbErrorsNotificationEnabled=*/ true);
+        detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnDpLinkTrainingFailure());
+        verify(mMockedUsbManager).registerDisplayPortAltModeInfoListener(any(), any());
+        verify(mMockedListener, never()).onCableNotCapableDisplayPort();
+        verify(mMockedListener).onDisplayPortLinkTrainingFailure();
+    }
+
+    private ConnectedDisplayUsbErrorsDetector createErrorsDetector(
+            final boolean isUsbManagerAvailable,
+            final boolean isConnectedDisplayUsbErrorsNotificationEnabled) {
+        when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled())
+                .thenReturn(isConnectedDisplayUsbErrorsNotificationEnabled);
+        when(mMockedInjector.getUsbManager()).thenReturn(
+                (isUsbManagerAvailable) ? mMockedUsbManager : null);
+        var detector = new ConnectedDisplayUsbErrorsDetector(mMockedFlags,
+                ApplicationProvider.getApplicationContext(), mMockedInjector);
+        detector.registerListener(mMockedListener);
+        return detector;
+    }
+
+    private DisplayPortAltModeInfo createInfoOnUsbCableNotCapableDp() {
+        return new DisplayPortAltModeInfo(
+                    DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED,
+                    DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE, -1, false, 0);
+    }
+
+    private DisplayPortAltModeInfo createInfoOnDpLinkTrainingFailure() {
+        return new DisplayPortAltModeInfo(
+                    DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED,
+                    DISPLAYPORT_ALT_MODE_STATUS_ENABLED, -1, false,
+                    LINK_TRAINING_STATUS_FAILURE);
+    }
+}
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..1d2034b
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.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.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 testNotificationOnDisplayPortLinkTrainingFailure() {
+        var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true,
+                /*isErrorHandlingEnabled=*/ true);
+        dnm.onDisplayPortLinkTrainingFailure();
+        assertExpectedNotification();
+    }
+
+    @Test
+    public void testNotificationOnCableNotCapableDisplayPort() {
+        var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true,
+                /*isErrorHandlingEnabled=*/ true);
+        dnm.onCableNotCapableDisplayPort();
+        assertExpectedNotification();
+    }
+
+    @Test
+    public void testNoErrorNotification(
+            @TestParameter final boolean isNotificationManagerAvailable,
+            @TestParameter final boolean isErrorHandlingEnabled) {
+        /* This case is tested by #testNotificationOnHotplugConnectionError,
+            #testNotificationOnDisplayPortLinkTrainingFailure,
+            #testNotificationOnCableNotCapableDisplayPort */
+        assumeFalse(isNotificationManagerAvailable && isErrorHandlingEnabled);
+        var dnm = createDisplayNotificationManager(isNotificationManagerAvailable,
+                isErrorHandlingEnabled);
+        // None of these methods should trigger a notification now.
+        dnm.onHotplugConnectionError();
+        dnm.onDisplayPortLinkTrainingFailure();
+        dnm.onCableNotCapableDisplayPort();
+        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);
+        // Usb errors detector is tested in ConnectedDisplayUsbErrorsDetectorTest
+        when(mMockedInjector.getUsbErrorsDetector()).thenReturn(/* usbErrorsDetector= */ 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/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index 7cc01e1..4329b6f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -160,6 +160,8 @@
         mPrefetchController = new PrefetchController(mJobSchedulerService);
         mPcConstants = mPrefetchController.getPcConstants();
 
+        setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);
+
         setUidBias(Process.myUid(), JobInfo.BIAS_DEFAULT);
 
         verify(mUsageStatsManagerInternal)
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java
new file mode 100644
index 0000000..2003d04
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.PersistableBundle;
+import android.util.Xml;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.PowerStats;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.text.ParseException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AggregatedPowerStatsTest {
+    private static final int TEST_POWER_COMPONENT = 1077;
+    private static final int APP_1 = 27;
+    private static final int APP_2 = 42;
+
+    private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+    private PowerStats.Descriptor mPowerComponentDescriptor;
+
+    @Before
+    public void setup() throws ParseException {
+        mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig();
+        mAggregatedPowerStatsConfig.trackPowerComponent(TEST_POWER_COMPONENT)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
+
+        mPowerComponentDescriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "fan", 2, 3,
+                PersistableBundle.forPair("speed", "fast"));
+    }
+
+    @Test
+    public void aggregation() {
+        AggregatedPowerStats stats = prepareAggregatePowerStats();
+
+        verifyAggregatedPowerStats(stats);
+    }
+
+    @Test
+    public void xmlPersistence() throws Exception {
+        AggregatedPowerStats stats = prepareAggregatePowerStats();
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        TypedXmlSerializer serializer = Xml.newFastSerializer();
+        serializer.setOutput(baos, "UTF-8");
+        stats.writeXml(serializer);
+        serializer.flush();
+
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new ByteArrayInputStream(baos.toByteArray()), "UTF-8");
+        AggregatedPowerStats actualStats = AggregatedPowerStats.createFromXml(parser,
+                mAggregatedPowerStatsConfig);
+
+        verifyAggregatedPowerStats(actualStats);
+    }
+
+    private AggregatedPowerStats prepareAggregatePowerStats() {
+        AggregatedPowerStats stats = new AggregatedPowerStats(mAggregatedPowerStatsConfig);
+        stats.addClockUpdate(1000, 456);
+        stats.setDuration(789);
+
+        stats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN,
+                AggregatedPowerStatsConfig.SCREEN_STATE_ON, 2000);
+        stats.setUidState(APP_1, AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
+                BatteryConsumer.PROCESS_STATE_CACHED, 2000);
+        stats.setUidState(APP_2, AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND, 2000);
+
+        PowerStats ps = new PowerStats(mPowerComponentDescriptor);
+        ps.stats[0] = 100;
+        ps.stats[1] = 987;
+
+        ps.uidStats.put(APP_1, new long[]{389, 0, 739});
+        ps.uidStats.put(APP_2, new long[]{278, 314, 628});
+
+        stats.addPowerStats(ps, 3000);
+
+        stats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN,
+                AggregatedPowerStatsConfig.SCREEN_STATE_OTHER, 4000);
+        stats.setUidState(APP_2, AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
+                BatteryConsumer.PROCESS_STATE_BACKGROUND, 4000);
+
+        ps.stats[0] = 444;
+        ps.stats[1] = 0;
+
+        ps.uidStats.put(APP_1, new long[]{0, 0, 400});
+        ps.uidStats.put(APP_2, new long[]{100, 200, 300});
+
+        stats.addPowerStats(ps, 5000);
+
+        return stats;
+    }
+
+    private void verifyAggregatedPowerStats(AggregatedPowerStats stats) {
+        PowerStats.Descriptor descriptor = stats.getPowerComponentStats(TEST_POWER_COMPONENT)
+                .getPowerStatsDescriptor();
+        assertThat(descriptor.powerComponentId).isEqualTo(TEST_POWER_COMPONENT);
+        assertThat(descriptor.name).isEqualTo("fan");
+        assertThat(descriptor.statsArrayLength).isEqualTo(2);
+        assertThat(descriptor.uidStatsArrayLength).isEqualTo(3);
+        assertThat(descriptor.extras.getString("speed")).isEqualTo("fast");
+
+        assertThat(getDeviceStats(stats,
+                AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                AggregatedPowerStatsConfig.SCREEN_STATE_ON))
+                .isEqualTo(new long[]{322, 987});
+
+        assertThat(getDeviceStats(stats,
+                AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                AggregatedPowerStatsConfig.SCREEN_STATE_OTHER))
+                .isEqualTo(new long[]{222, 0});
+
+        assertThat(getUidDeviceStats(stats,
+                APP_1,
+                AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+                BatteryConsumer.PROCESS_STATE_UNSPECIFIED))
+                .isEqualTo(new long[]{259, 0, 492});
+
+        assertThat(getUidDeviceStats(stats,
+                APP_1,
+                AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+                BatteryConsumer.PROCESS_STATE_CACHED))
+                .isEqualTo(new long[]{129, 0, 446});
+
+        assertThat(getUidDeviceStats(stats,
+                APP_1,
+                AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
+                BatteryConsumer.PROCESS_STATE_CACHED))
+                .isEqualTo(new long[]{0, 0, 200});
+
+        assertThat(getUidDeviceStats(stats,
+                APP_2,
+                AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+                BatteryConsumer.PROCESS_STATE_UNSPECIFIED))
+                .isEqualTo(new long[]{185, 209, 418});
+
+        assertThat(getUidDeviceStats(stats,
+                APP_2,
+                AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND))
+                .isEqualTo(new long[]{142, 204, 359});
+
+        assertThat(getUidDeviceStats(stats,
+                APP_2,
+                AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
+                BatteryConsumer.PROCESS_STATE_BACKGROUND))
+                .isEqualTo(new long[]{50, 100, 150});
+    }
+
+    private static long[] getDeviceStats(AggregatedPowerStats stats, int... states) {
+        long[] out = new long[states.length];
+        stats.getPowerComponentStats(TEST_POWER_COMPONENT).getDeviceStats(out, states);
+        return out;
+    }
+
+    private static long[] getUidDeviceStats(AggregatedPowerStats stats, int uid, int... states) {
+        long[] out = new long[states.length];
+        stats.getPowerComponentStats(TEST_POWER_COMPONENT).getUidStats(out, uid, states);
+        return out;
+    }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
index 5a2d2e3..663af5d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
@@ -42,6 +42,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.os.Clock;
 import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.PowerProfile;
 
@@ -214,6 +215,7 @@
 
     public class TestBatteryStatsImpl extends BatteryStatsImpl {
         public TestBatteryStatsImpl(Context context) {
+            super(Clock.SYSTEM_CLOCK, null);
             mPowerProfile = new PowerProfile(context, true /* forTest */);
 
             SparseArray<int[]> cpusByPolicy = new SparseArray<>();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
index 77124d0..abb3be7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
@@ -113,15 +113,15 @@
     public void constrainedIteration() {
         prepareHistory();
 
-        // Initial time is 3000
+        // Initial time is 1000_000
         assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 0),
-                3_000L, 3_000L, 1003_000L, 2003_000L, 2004_000L);
-        assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(1000_000, 0),
-                1003_000L, 2003_000L, 2004_000L);
-        assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 2000_000L),
-                3_000L, 3_000L, 1003_000L);
+                1000_000L, 1000_000L, 2000_000L, 3000_000L, 3001_000L);
+        assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(2000_000, 0),
+                2000_000L, 3000_000L, 3001_000L);
+        assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 3000_000L),
+                1000_000L, 1000_000L, 2000_000L);
         assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(1003_000L, 2004_000L),
-                1003_000L, 2003_000L);
+                2000_000L);
     }
 
     private void prepareHistory() {
@@ -144,7 +144,7 @@
         ArrayList<Long> actualTimestamps = new ArrayList<>();
         while (iterator.hasNext()) {
             BatteryStats.HistoryItem item = iterator.next();
-            actualTimestamps.add(item.currentTime);
+            actualTimestamps.add(item.time);
         }
         assertThat(actualTimestamps).isEqualTo(Arrays.asList(expectedTimestamps));
     }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index f22296a..1dd499c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -41,6 +41,7 @@
 
 import com.android.internal.os.BatteryStatsHistory;
 import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerStats;
 
 import org.junit.Before;
@@ -69,6 +70,7 @@
     private File mSystemDir;
     private File mHistoryDir;
     private final MockClock mClock = new MockClock();
+    private final MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
     private BatteryStatsHistory mHistory;
     private BatteryStats.HistoryPrinter mHistoryPrinter;
     @Mock
@@ -94,7 +96,7 @@
         mClock.realtime = 123;
 
         mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
-                mStepDetailsCalculator, mClock, mTracer) {
+                mStepDetailsCalculator, mClock, mMonotonicClock, mTracer) {
             @Override
             public boolean readFileToParcel(Parcel out, AtomicFile file) {
                 mReadFiles.add(file.getBaseFile().getName());
@@ -210,7 +212,7 @@
             mClock.realtime = 1000 * i;
             fileList.add(mClock.realtime + ".bh");
 
-            mHistory.startNextFile();
+            mHistory.startNextFile(mClock.realtime);
             createActiveFile(mHistory);
             verifyFileNames(mHistory, fileList);
             verifyActiveFile(mHistory, mClock.realtime + ".bh");
@@ -218,7 +220,7 @@
 
         // create file 32
         mClock.realtime = 1000 * 32;
-        mHistory.startNextFile();
+        mHistory.startNextFile(mClock.realtime);
         createActiveFile(mHistory);
         fileList.add("32000.bh");
         fileList.remove(0);
@@ -229,7 +231,7 @@
 
         // create file 33
         mClock.realtime = 1000 * 33;
-        mHistory.startNextFile();
+        mHistory.startNextFile(mClock.realtime);
         createActiveFile(mHistory);
         // verify file 1 is deleted
         fileList.add("33000.bh");
@@ -240,7 +242,7 @@
 
         // create a new BatteryStatsHistory object, it will pick up existing history files.
         BatteryStatsHistory history2 = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
-                null, mClock, mTracer);
+                null, mClock, mMonotonicClock, mTracer);
         // verify constructor can pick up all files from file system.
         verifyFileNames(history2, fileList);
         verifyActiveFile(history2, "33000.bh");
@@ -262,7 +264,7 @@
         // create file 1.
         mClock.realtime = 2345678;
 
-        history2.startNextFile();
+        history2.startNextFile(mClock.realtime);
         createActiveFile(history2);
         verifyFileNames(history2, Arrays.asList("1234567.bh", "2345678.bh"));
         verifyActiveFile(history2, "2345678.bh");
@@ -336,14 +338,14 @@
         mHistory.recordEvent(mClock.realtime, mClock.uptime,
                 BatteryStats.HistoryItem.EVENT_JOB_START, "job", 42);
 
-        mHistory.startNextFile();       // 1000.bh
+        mHistory.startNextFile(mClock.realtime);       // 1000.bh
 
         mClock.realtime = 2000;
         mClock.uptime = 2000;
         mHistory.recordEvent(mClock.realtime, mClock.uptime,
                 BatteryStats.HistoryItem.EVENT_JOB_FINISH, "job", 42);
 
-        mHistory.startNextFile();       // 2000.bh
+        mHistory.startNextFile(mClock.realtime);       // 2000.bh
 
         mClock.realtime = 3000;
         mClock.uptime = 3000;
@@ -351,7 +353,7 @@
                 HistoryItem.EVENT_ALARM, "alarm", 42);
 
         // Flush accumulated history to disk
-        mHistory.startNextFile();
+        mHistory.startNextFile(mClock.realtime);
     }
 
     private void verifyActiveFile(BatteryStatsHistory history, String file) {
@@ -518,7 +520,7 @@
         // Keep the preserved part of history short - we only need to capture the very tail of
         // history.
         mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 1, 6000,
-                mStepDetailsCalculator, mClock, mTracer);
+                mStepDetailsCalculator, mClock, mMonotonicClock, mTracer);
 
         mHistory.forceRecordAllHistory();
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index 5df0acb..b1da1fc 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -40,6 +40,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerProfile;
 
 import org.junit.Rule;
@@ -64,6 +65,8 @@
             new BatteryUsageStatsRule(12345, mHistoryDir)
                     .setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0)
                     .setAveragePower(PowerProfile.POWER_AUDIO, 720.0);
+    private MockClock mMockClock = mStatsRule.getMockClock();
+
     @Test
     public void test_getBatteryUsageStats() {
         BatteryStatsImpl batteryStats = prepareBatteryStats();
@@ -369,17 +372,23 @@
     public void testAggregateBatteryStats() {
         Context context = InstrumentationRegistry.getContext();
         BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
-        mStatsRule.setCurrentTime(5 * MINUTE_IN_MS);
+        MonotonicClock monotonicClock = new MonotonicClock(0, mStatsRule.getMockClock());
+
+        setTime(5 * MINUTE_IN_MS);
         synchronized (batteryStats) {
             batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
         }
-        BatteryUsageStatsStore batteryUsageStatsStore = new BatteryUsageStatsStore(context,
-                batteryStats, new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"),
-                new TestHandler(), Integer.MAX_VALUE);
-        batteryUsageStatsStore.onSystemReady();
+
+        PowerStatsStore powerStatsStore = new PowerStatsStore(
+                new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"),
+                new TestHandler(), null);
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context,
-                batteryStats, batteryUsageStatsStore);
+                batteryStats, powerStatsStore);
+
+        batteryStats.setBatteryResetListener(reason ->
+                powerStatsStore.storeBatteryUsageStats(monotonicClock.monotonicTime(),
+                        provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT)));
 
         synchronized (batteryStats) {
             batteryStats.noteFlashlightOnLocked(APP_UID,
@@ -389,7 +398,7 @@
             batteryStats.noteFlashlightOffLocked(APP_UID,
                     20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
         }
-        mStatsRule.setCurrentTime(25 * MINUTE_IN_MS);
+        setTime(25 * MINUTE_IN_MS);
         synchronized (batteryStats) {
             batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
         }
@@ -402,7 +411,7 @@
             batteryStats.noteFlashlightOffLocked(APP_UID,
                     50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
         }
-        mStatsRule.setCurrentTime(55 * MINUTE_IN_MS);
+        setTime(55 * MINUTE_IN_MS);
         synchronized (batteryStats) {
             batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
         }
@@ -416,7 +425,7 @@
             batteryStats.noteFlashlightOffLocked(APP_UID,
                     70 * MINUTE_IN_MS, 70 * MINUTE_IN_MS);
         }
-        mStatsRule.setCurrentTime(75 * MINUTE_IN_MS);
+        setTime(75 * MINUTE_IN_MS);
         synchronized (batteryStats) {
             batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
         }
@@ -430,7 +439,7 @@
             batteryStats.noteFlashlightOffLocked(APP_UID,
                     90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS);
         }
-        mStatsRule.setCurrentTime(95 * MINUTE_IN_MS);
+        setTime(95 * MINUTE_IN_MS);
 
         // Include the first and the second snapshot, but not the third or current
         BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
@@ -457,29 +466,41 @@
                 .of(180.0);
     }
 
+    private void setTime(long timeMs) {
+        mMockClock.currentTime = timeMs;
+        mMockClock.realtime = timeMs;
+    }
+
     @Test
     public void testAggregateBatteryStats_incompatibleSnapshot() {
         Context context = InstrumentationRegistry.getContext();
         MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
         batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
 
-        BatteryUsageStatsStore batteryUsageStatsStore = mock(BatteryUsageStatsStore.class);
+        PowerStatsStore powerStatsStore = mock(PowerStatsStore.class);
 
-        when(batteryUsageStatsStore.listBatteryUsageStatsTimestamps())
-                .thenReturn(new long[]{1000, 2000});
-
-        when(batteryUsageStatsStore.loadBatteryUsageStats(1000)).thenReturn(
+        PowerStatsSpan span0 = new PowerStatsSpan(0);
+        span0.addTimeFrame(0, 1000, 1234);
+        span0.addSection(new BatteryUsageStatsSection(
                 new BatteryUsageStats.Builder(batteryStats.getCustomEnergyConsumerNames())
-                        .setStatsDuration(1234).build());
+                        .setStatsDuration(1234).build()));
 
-        // Add a snapshot, with a different set of custom power components.  It should
-        // be skipped by the aggregation.
-        when(batteryUsageStatsStore.loadBatteryUsageStats(2000)).thenReturn(
+        PowerStatsSpan span1 = new PowerStatsSpan(1);
+        span1.addTimeFrame(0, 2000, 4321);
+        span1.addSection(new BatteryUsageStatsSection(
                 new BatteryUsageStats.Builder(new String[]{"different"})
-                        .setStatsDuration(4321).build());
+                        .setStatsDuration(4321).build()));
+
+        when(powerStatsStore.getTableOfContents()).thenReturn(
+                List.of(span0.getMetadata(), span1.getMetadata()));
+
+        when(powerStatsStore.loadPowerStatsSpan(0, BatteryUsageStatsSection.TYPE))
+                .thenReturn(span0);
+        when(powerStatsStore.loadPowerStatsSpan(1, BatteryUsageStatsSection.TYPE))
+                .thenReturn(span1);
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context,
-                batteryStats, batteryUsageStatsStore);
+                batteryStats, powerStatsStore);
 
         BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
                 .aggregateSnapshots(0, 3000)
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 93cbea6..3579fce 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -88,6 +88,10 @@
         mBatteryStats.onSystemReady();
     }
 
+    public MockClock getMockClock() {
+        return mMockClock;
+    }
+
     public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) {
         mPowerProfile.forceInitForTesting(mContext, xmlId);
         return this;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java
deleted file mode 100644
index b846e3a..0000000
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.power.stats;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-
-import android.content.Context;
-import android.os.BatteryManager;
-import android.os.BatteryUsageStats;
-import android.os.BatteryUsageStatsQuery;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Xml;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.os.PowerProfile;
-import com.android.modules.utils.TypedXmlSerializer;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-
-@RunWith(AndroidJUnit4.class)
-@SuppressWarnings("GuardedBy")
-public class BatteryUsageStatsStoreTest {
-    private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024;
-
-    private final MockClock mMockClock = new MockClock();
-    private MockBatteryStatsImpl mBatteryStats;
-    private BatteryUsageStatsStore mBatteryUsageStatsStore;
-    private BatteryUsageStatsProvider mBatteryUsageStatsProvider;
-    private File mStoreDirectory;
-
-    @Before
-    public void setup() {
-        mMockClock.currentTime = 123;
-        mBatteryStats = new MockBatteryStatsImpl(mMockClock);
-        mBatteryStats.setNoAutoReset(true);
-        mBatteryStats.setPowerProfile(mock(PowerProfile.class));
-        mBatteryStats.onSystemReady();
-
-        Context context = InstrumentationRegistry.getContext();
-
-        mStoreDirectory = new File(context.getCacheDir(), "BatteryUsageStatsStoreTest");
-        clearDirectory(mStoreDirectory);
-
-        mBatteryUsageStatsStore = new BatteryUsageStatsStore(context, mBatteryStats,
-                mStoreDirectory, new TestHandler(), MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
-        mBatteryUsageStatsStore.onSystemReady();
-
-        mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats);
-    }
-
-    @Test
-    public void testStoreSnapshot() {
-        mMockClock.currentTime = 1_600_000;
-        mMockClock.realtime = 1000;
-        mMockClock.uptime = 1000;
-
-        prepareBatteryStats();
-
-        mMockClock.realtime = 1_000_000;
-        mMockClock.uptime = 1_000_000;
-        mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
-
-        final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps();
-        assertThat(timestamps).hasLength(1);
-        assertThat(timestamps[0]).isEqualTo(1_600_000);
-
-        final BatteryUsageStats batteryUsageStats = mBatteryUsageStatsStore.loadBatteryUsageStats(
-                1_600_000);
-        assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(123);
-        assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(1_600_000);
-        assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000);
-        assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(5);
-        assertThat(batteryUsageStats.getDischargeDurationMs()).isEqualTo(1_000_000 - 1_000);
-        assertThat(batteryUsageStats.getAggregateBatteryConsumer(
-                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE).getConsumedPower())
-                .isEqualTo(600);  // (3_600_000 - 3_000_000) / 1000
-    }
-
-    @Test
-    public void testGarbageCollectOldSnapshots() throws Exception {
-        prepareBatteryStats();
-
-        mMockClock.realtime = 10_000_000;
-        mMockClock.uptime = 10_000_000;
-        mMockClock.currentTime = 10_000_000;
-
-        final int snapshotFileSize = getSnapshotFileSize();
-        final int numberOfSnapshots =
-                (int) (MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES / snapshotFileSize);
-        for (int i = 0; i < numberOfSnapshots + 2; i++) {
-            mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
-
-            mMockClock.realtime += 10_000_000;
-            mMockClock.uptime += 10_000_000;
-            mMockClock.currentTime += 10_000_000;
-            prepareBatteryStats();
-        }
-
-        final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps();
-        Arrays.sort(timestamps);
-        assertThat(timestamps).hasLength(numberOfSnapshots);
-        // Two snapshots (10_000_000 and 20_000_000) should have been discarded
-        assertThat(timestamps[0]).isEqualTo(30_000_000);
-        assertThat(getDirectorySize(mStoreDirectory))
-                .isAtMost(MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
-    }
-
-    @Test
-    public void testRemoveAllSnapshots() throws Exception {
-        prepareBatteryStats();
-
-        for (int i = 0; i < 3; i++) {
-            mMockClock.realtime += 10_000_000;
-            mMockClock.uptime += 10_000_000;
-            mMockClock.currentTime += 10_000_000;
-            prepareBatteryStats();
-
-            mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
-        }
-
-        assertThat(getDirectorySize(mStoreDirectory)).isNotEqualTo(0);
-
-        mBatteryUsageStatsStore.removeAllSnapshots();
-
-        assertThat(getDirectorySize(mStoreDirectory)).isEqualTo(0);
-    }
-
-    @Test
-    public void testSavingStatsdAtomPullTimestamp() {
-        mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(1234);
-        assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
-                .isEqualTo(1234);
-        mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(5478);
-        assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
-                .isEqualTo(5478);
-    }
-
-    private void prepareBatteryStats() {
-        mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
-                /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0,
-                mMockClock.realtime, mMockClock.uptime, mMockClock.currentTime);
-        mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
-                /* plugType */ 0, 85, 72, 3700, 3_000_000, 4_000_000, 0,
-                mMockClock.realtime + 500_000, mMockClock.uptime + 500_000,
-                mMockClock.currentTime + 500_000);
-    }
-
-    private void clearDirectory(File dir) {
-        if (dir.exists()) {
-            for (File child : dir.listFiles()) {
-                if (child.isDirectory()) {
-                    clearDirectory(child);
-                }
-                child.delete();
-            }
-        }
-    }
-
-    private long getDirectorySize(File dir) {
-        long size = 0;
-        if (dir.exists()) {
-            for (File child : dir.listFiles()) {
-                if (child.isDirectory()) {
-                    size += getDirectorySize(child);
-                } else {
-                    size += child.length();
-                }
-            }
-        }
-        return size;
-    }
-
-    private int getSnapshotFileSize() throws IOException {
-        BatteryUsageStats stats = mBatteryUsageStatsProvider.getBatteryUsageStats(
-                new BatteryUsageStatsQuery.Builder()
-                        .setMaxStatsAgeMs(0)
-                        .includePowerModels()
-                        .build());
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
-        TypedXmlSerializer serializer = Xml.newBinarySerializer();
-        serializer.setOutput(out, StandardCharsets.UTF_8.name());
-        serializer.startDocument(null, true);
-        stats.writeXml(serializer);
-        serializer.endDocument();
-        return out.toByteArray().length;
-    }
-
-    private static class TestHandler extends Handler {
-        TestHandler() {
-            super(Looper.getMainLooper());
-        }
-
-        @Override
-        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
-            msg.getCallback().run();
-            return true;
-        }
-    }
-}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
index 4ecee9f..30a73181 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
@@ -122,7 +122,7 @@
         // 4 * 10 = 40 bits needed to represent the composite state
         MultiStateStats.States[] states = new MultiStateStats.States[10];
         for (int i = 0; i < states.length; i++) {
-            states[i] = new MultiStateStats.States(true, labels);
+            states[i] = new MultiStateStats.States("foo", true, labels);
         }
         IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
                 () -> new MultiStateStats.Factory(DIMENSION_COUNT, states));
@@ -191,8 +191,8 @@
     private static MultiStateStats.Factory makeFactory(boolean trackBatteryState,
             boolean trackProcState, boolean trackScreenState) {
         return new MultiStateStats.Factory(DIMENSION_COUNT,
-                new MultiStateStats.States(trackBatteryState, "plugged-in", "on-battery"),
-                new MultiStateStats.States(trackProcState,
+                new MultiStateStats.States("bs", trackBatteryState, "plugged-in", "on-battery"),
+                new MultiStateStats.States("ps", trackProcState,
                         BatteryConsumer.processStateToString(
                                 BatteryConsumer.PROCESS_STATE_UNSPECIFIED),
                         BatteryConsumer.processStateToString(
@@ -203,7 +203,7 @@
                                 BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE),
                         BatteryConsumer.processStateToString(
                                 BatteryConsumer.PROCESS_STATE_CACHED)),
-                new MultiStateStats.States(trackScreenState, "screen-off", "plugged-in"));
+                new MultiStateStats.States("scr", trackScreenState, "screen-off", "plugged-in"));
     }
 
     private FactorySubject assertThatCpuPerformanceStatsFactory(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
index 47de443..b52fc8a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
@@ -24,11 +24,14 @@
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 import android.os.PersistableBundle;
+import android.text.format.DateFormat;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerStats;
 
 import org.junit.Before;
@@ -36,16 +39,19 @@
 import org.junit.runner.RunWith;
 
 import java.text.ParseException;
-import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.List;
+import java.util.TimeZone;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class PowerStatsAggregatorTest {
     private static final int TEST_POWER_COMPONENT = 77;
     private static final int TEST_UID = 42;
+    private static final long START_TIME = 1234;
 
     private final MockClock mClock = new MockClock();
-    private long mStartTime;
+    private final MonotonicClock mMonotonicClock = new MonotonicClock(START_TIME, mClock);
     private BatteryStatsHistory mHistory;
     private PowerStatsAggregator mAggregator;
     private int mAggregatedStatsCount;
@@ -53,25 +59,25 @@
     @Before
     public void setup() throws ParseException {
         mHistory = new BatteryStatsHistory(32, 1024,
-                mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock);
-        mStartTime = new SimpleDateFormat("yyyy-MM-dd HH:mm")
-                .parse("2008-09-23 08:00").getTime();
-        mClock.currentTime = mStartTime;
+                mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock,
+                mMonotonicClock);
 
-        PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory);
-        builder.trackPowerComponent(TEST_POWER_COMPONENT)
+        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+        config.trackPowerComponent(TEST_POWER_COMPONENT)
                 .trackDeviceStates(
-                        PowerStatsAggregator.STATE_POWER,
-                        PowerStatsAggregator.STATE_SCREEN)
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
                 .trackUidStates(
-                        PowerStatsAggregator.STATE_POWER,
-                        PowerStatsAggregator.STATE_SCREEN,
-                        PowerStatsAggregator.STATE_PROCESS_STATE);
-        mAggregator = builder.build();
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
+        mAggregator = new PowerStatsAggregator(config, mHistory);
     }
 
     @Test
     public void stateUpdates() {
+        mClock.currentTime = 1222156800000L;    // An important date in world history
+
         mHistory.forceRecordAllHistory();
         mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true);
         mHistory.recordStateStartEvent(mClock.realtime, mClock.uptime,
@@ -98,15 +104,32 @@
         mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID,
                 BatteryConsumer.PROCESS_STATE_BACKGROUND);
 
-        advance(3000);
+        advance(1000);
+
+        mClock.currentTime += 60 * 60 * 1000;       // one hour
+        mHistory.recordCurrentTimeChange(mClock.realtime, mClock.uptime, mClock.currentTime);
+
+        advance(2000);
 
         powerStats.stats = new long[]{20000};
         powerStats.uidStats.put(TEST_UID, new long[]{4444});
         mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
 
-        mAggregator.aggregateBatteryStats(0, 0, stats -> {
+        mAggregator.aggregatePowerStats(0, 0, stats -> {
             assertThat(mAggregatedStatsCount++).isEqualTo(0);
-            assertThat(stats.getStartTime()).isEqualTo(mStartTime);
+            assertThat(stats.getStartTime()).isEqualTo(START_TIME);
+
+            List<AggregatedPowerStats.ClockUpdate> clockUpdates = stats.getClockUpdates();
+            assertThat(clockUpdates).hasSize(2);
+
+            AggregatedPowerStats.ClockUpdate clockUpdate0 = clockUpdates.get(0);
+            assertThat(clockUpdate0.monotonicTime).isEqualTo(1234);
+            assertThat(formatDateTime(clockUpdate0.currentTime)).isEqualTo("2008-09-23 08:00:00");
+
+            AggregatedPowerStats.ClockUpdate clockUpdate1 = clockUpdates.get(1);
+            assertThat(clockUpdate1.monotonicTime).isEqualTo(1234 + 3000);
+            assertThat(formatDateTime(clockUpdate1.currentTime)).isEqualTo("2008-09-23 09:00:03");
+
             assertThat(stats.getDuration()).isEqualTo(5000);
 
             long[] values = new long[1];
@@ -115,40 +138,47 @@
                     TEST_POWER_COMPONENT);
 
             assertThat(powerComponentStats.getDeviceStats(values, new int[]{
-                    PowerStatsAggregator.POWER_STATE_OTHER,
-                    PowerStatsAggregator.SCREEN_STATE_ON}))
+                    AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+                    AggregatedPowerStatsConfig.SCREEN_STATE_ON}))
                     .isTrue();
             assertThat(values).isEqualTo(new long[]{10000});
 
             assertThat(powerComponentStats.getDeviceStats(values, new int[]{
-                    PowerStatsAggregator.POWER_STATE_BATTERY,
-                    PowerStatsAggregator.SCREEN_STATE_OTHER}))
+                    AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                    AggregatedPowerStatsConfig.SCREEN_STATE_OTHER}))
                     .isTrue();
             assertThat(values).isEqualTo(new long[]{20000});
 
             assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
-                    PowerStatsAggregator.POWER_STATE_OTHER,
-                    PowerStatsAggregator.SCREEN_STATE_ON,
+                    AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+                    AggregatedPowerStatsConfig.SCREEN_STATE_ON,
                     BatteryConsumer.PROCESS_STATE_FOREGROUND}))
                     .isTrue();
             assertThat(values).isEqualTo(new long[]{1234});
 
             assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
-                    PowerStatsAggregator.POWER_STATE_BATTERY,
-                    PowerStatsAggregator.SCREEN_STATE_OTHER,
+                    AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                    AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
                     BatteryConsumer.PROCESS_STATE_FOREGROUND}))
                     .isTrue();
             assertThat(values).isEqualTo(new long[]{1111});
 
             assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
-                    PowerStatsAggregator.POWER_STATE_BATTERY,
-                    PowerStatsAggregator.SCREEN_STATE_OTHER,
+                    AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                    AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
                     BatteryConsumer.PROCESS_STATE_BACKGROUND}))
                     .isTrue();
             assertThat(values).isEqualTo(new long[]{3333});
         });
     }
 
+    @NonNull
+    private static CharSequence formatDateTime(long timeInMillis) {
+        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+        cal.setTimeInMillis(timeInMillis);
+        return DateFormat.format("yyyy-MM-dd hh:mm:ss", cal);
+    }
+
     @Test
     public void incompatiblePowerStats() {
         mHistory.forceRecordAllHistory();
@@ -181,39 +211,39 @@
 
         mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 50, /* plugged */ true);
 
-        mAggregator.aggregateBatteryStats(0, 0, stats -> {
+        mAggregator.aggregatePowerStats(0, 0, stats -> {
             long[] values = new long[1];
 
             PowerComponentAggregatedPowerStats powerComponentStats =
                     stats.getPowerComponentStats(TEST_POWER_COMPONENT);
 
             if (mAggregatedStatsCount == 0) {
-                assertThat(stats.getStartTime()).isEqualTo(mStartTime);
+                assertThat(stats.getStartTime()).isEqualTo(START_TIME);
                 assertThat(stats.getDuration()).isEqualTo(2000);
 
                 assertThat(powerComponentStats.getDeviceStats(values, new int[]{
-                        PowerStatsAggregator.POWER_STATE_OTHER,
-                        PowerStatsAggregator.SCREEN_STATE_ON}))
+                        AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+                        AggregatedPowerStatsConfig.SCREEN_STATE_ON}))
                         .isTrue();
                 assertThat(values).isEqualTo(new long[]{10000});
                 assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
-                        PowerStatsAggregator.POWER_STATE_OTHER,
-                        PowerStatsAggregator.SCREEN_STATE_ON,
+                        AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+                        AggregatedPowerStatsConfig.SCREEN_STATE_ON,
                         BatteryConsumer.PROCESS_STATE_FOREGROUND}))
                         .isTrue();
                 assertThat(values).isEqualTo(new long[]{1234});
             } else if (mAggregatedStatsCount == 1) {
-                assertThat(stats.getStartTime()).isEqualTo(mStartTime + 2000);
+                assertThat(stats.getStartTime()).isEqualTo(START_TIME + 2000);
                 assertThat(stats.getDuration()).isEqualTo(1000);
 
                 assertThat(powerComponentStats.getDeviceStats(values, new int[]{
-                        PowerStatsAggregator.POWER_STATE_BATTERY,
-                        PowerStatsAggregator.SCREEN_STATE_ON}))
+                        AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                        AggregatedPowerStatsConfig.SCREEN_STATE_ON}))
                         .isTrue();
                 assertThat(values).isEqualTo(new long[]{20000});
                 assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
-                        PowerStatsAggregator.POWER_STATE_BATTERY,
-                        PowerStatsAggregator.SCREEN_STATE_ON,
+                        AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+                        AggregatedPowerStatsConfig.SCREEN_STATE_ON,
                         BatteryConsumer.PROCESS_STATE_FOREGROUND}))
                         .isTrue();
                 assertThat(values).isEqualTo(new long[]{4444});
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
new file mode 100644
index 0000000..0e58787
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
@@ -0,0 +1,269 @@
+/*
+ * 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.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.BatteryConsumer;
+import android.os.BatteryManager;
+import android.os.BatteryUsageStats;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.MonotonicClock;
+import com.android.internal.os.PowerProfile;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+@RunWith(AndroidJUnit4.class)
+public class PowerStatsSchedulerTest {
+    private PowerStatsStore mPowerStatsStore;
+    private Handler mHandler;
+    private MockClock mClock = new MockClock();
+    private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
+    private MockBatteryStatsImpl mBatteryStats;
+    private BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+    private PowerStatsScheduler mPowerStatsScheduler;
+    private PowerProfile mPowerProfile;
+    private PowerStatsAggregator mPowerStatsAggregator;
+    private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+
+    @Before
+    public void setup() {
+        final Context context = InstrumentationRegistry.getContext();
+
+        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+
+        mClock.currentTime = Instant.parse("2023-01-02T03:04:05.00Z").toEpochMilli();
+        mClock.realtime = 7654321;
+
+        HandlerThread bgThread = new HandlerThread("bg thread");
+        bgThread.start();
+        File systemDir = context.getCacheDir();
+        mHandler = new Handler(bgThread.getLooper());
+        mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig();
+        mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, mAggregatedPowerStatsConfig);
+        mPowerProfile = mock(PowerProfile.class);
+        when(mPowerProfile.getAveragePower(PowerProfile.POWER_FLASHLIGHT)).thenReturn(1000000.0);
+        mBatteryStats = new MockBatteryStatsImpl(mClock).setPowerProfile(mPowerProfile);
+        mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats);
+        mPowerStatsAggregator = mock(PowerStatsAggregator.class);
+        mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator,
+                TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1), mPowerStatsStore, mClock,
+                mMonotonicClock, mHandler, mBatteryStats, mBatteryUsageStatsProvider);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void storeAggregatePowerStats() {
+        mPowerStatsStore.reset();
+
+        assertThat(mPowerStatsStore.getTableOfContents()).isEmpty();
+
+        mPowerStatsStore.storeAggregatedPowerStats(
+                createAggregatedPowerStats(mMonotonicClock.monotonicTime(), mClock.currentTime,
+                        123));
+
+        long delayBeforeAggregating = TimeUnit.MINUTES.toMillis(90);
+        mClock.realtime += delayBeforeAggregating;
+        mClock.currentTime += delayBeforeAggregating;
+
+        doAnswer(invocation -> {
+            // The first span is longer than 30 min, because the end time is being aligned with
+            // the wall clock.  Subsequent spans should be precisely 30 minutes.
+            long startTime = invocation.getArgument(0);
+            long endTime = invocation.getArgument(1);
+            Consumer<AggregatedPowerStats> consumer = invocation.getArgument(2);
+
+            long startTimeWallClock =
+                    mClock.currentTime - (mMonotonicClock.monotonicTime() - startTime);
+            long endTimeWallClock =
+                    mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime);
+
+            assertThat(startTime).isEqualTo(7654321 + 123);
+            assertThat(endTime - startTime).isAtLeast(TimeUnit.MINUTES.toMillis(30));
+            assertThat(Instant.ofEpochMilli(endTimeWallClock))
+                    .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
+
+            consumer.accept(
+                    createAggregatedPowerStats(startTime, startTimeWallClock, endTime - startTime));
+            return null;
+        }).doAnswer(invocation -> {
+            long startTime = invocation.getArgument(0);
+            long endTime = invocation.getArgument(1);
+            Consumer<AggregatedPowerStats> consumer = invocation.getArgument(2);
+
+            long startTimeWallClock =
+                    mClock.currentTime - (mMonotonicClock.monotonicTime() - startTime);
+            long endTimeWallClock =
+                    mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime);
+
+            assertThat(Instant.ofEpochMilli(startTimeWallClock))
+                    .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
+            assertThat(Instant.ofEpochMilli(endTimeWallClock))
+                    .isEqualTo(Instant.parse("2023-01-02T04:30:00Z"));
+
+            consumer.accept(
+                    createAggregatedPowerStats(startTime, startTimeWallClock, endTime - startTime));
+            return null;
+        }).when(mPowerStatsAggregator).aggregatePowerStats(anyLong(), anyLong(),
+                any(Consumer.class));
+
+        mPowerStatsScheduler.schedulePowerStatsAggregation();
+        ConditionVariable done = new ConditionVariable();
+        mHandler.post(done::open);
+        done.block();
+
+        verify(mPowerStatsAggregator, times(2))
+                .aggregatePowerStats(anyLong(), anyLong(), any(Consumer.class));
+
+        List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents();
+        assertThat(contents).hasSize(3);
+        // Skip the first entry, which was placed in the store at the beginning of this test
+        PowerStatsSpan.TimeFrame timeFrame1 = contents.get(1).getTimeFrames().get(0);
+        PowerStatsSpan.TimeFrame timeFrame2 = contents.get(2).getTimeFrames().get(0);
+        assertThat(timeFrame1.startMonotonicTime).isEqualTo(7654321 + 123);
+        assertThat(timeFrame2.startMonotonicTime)
+                .isEqualTo(timeFrame1.startMonotonicTime + timeFrame1.duration);
+        assertThat(Instant.ofEpochMilli(timeFrame2.startTime))
+                .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
+        assertThat(Duration.ofMillis(timeFrame2.duration)).isEqualTo(Duration.ofMinutes(30));
+    }
+
+    private AggregatedPowerStats createAggregatedPowerStats(long monotonicTime, long currentTime,
+            long duration) {
+        AggregatedPowerStats stats = new AggregatedPowerStats(mAggregatedPowerStatsConfig);
+        stats.addClockUpdate(monotonicTime, currentTime);
+        stats.setDuration(duration);
+        return stats;
+    }
+
+    @Test
+    public void storeBatteryUsageStatsOnReset() {
+        mBatteryStats.forceRecordAllHistory();
+        synchronized (mBatteryStats) {
+            mBatteryStats.setOnBatteryLocked(mClock.realtime, mClock.uptime, true,
+                    BatteryManager.BATTERY_STATUS_DISCHARGING, 50, 0);
+        }
+
+        mPowerStatsScheduler.start(/* schedulePeriodicPowerStatsCollection */false);
+
+        assertThat(mPowerStatsStore.getTableOfContents()).isEmpty();
+
+        mPowerStatsScheduler.start(true);
+
+        synchronized (mBatteryStats) {
+            mBatteryStats.noteFlashlightOnLocked(42, mClock.realtime, mClock.uptime);
+        }
+
+        mClock.realtime += 60000;
+        mClock.currentTime += 60000;
+
+        synchronized (mBatteryStats) {
+            mBatteryStats.noteFlashlightOffLocked(42, mClock.realtime, mClock.uptime);
+        }
+
+        mClock.realtime += 60000;
+        mClock.currentTime += 60000;
+
+        // Battery stats reset should have the side-effect of saving accumulated battery usage stats
+        synchronized (mBatteryStats) {
+            mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
+        }
+
+        // Await completion
+        ConditionVariable done = new ConditionVariable();
+        mHandler.post(done::open);
+        done.block();
+
+        List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents();
+        assertThat(contents).hasSize(1);
+
+        PowerStatsSpan.Metadata metadata = contents.get(0);
+
+        PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(),
+                BatteryUsageStatsSection.TYPE);
+        assertThat(span).isNotNull();
+
+        List<PowerStatsSpan.TimeFrame> timeFrames = span.getMetadata().getTimeFrames();
+        assertThat(timeFrames).hasSize(1);
+        assertThat(timeFrames.get(0).startMonotonicTime).isEqualTo(7654321);
+        assertThat(timeFrames.get(0).duration).isEqualTo(120000);
+
+        List<PowerStatsSpan.Section> sections = span.getSections();
+        assertThat(sections).hasSize(1);
+
+        PowerStatsSpan.Section section = sections.get(0);
+        assertThat(section.getType()).isEqualTo(BatteryUsageStatsSection.TYPE);
+        BatteryUsageStats bus = ((BatteryUsageStatsSection) section).getBatteryUsageStats();
+        assertThat(bus.getAggregateBatteryConsumer(
+                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+                .isEqualTo(60000);
+    }
+
+    @Test
+    public void alignToWallClock() {
+        // Expect the aligned value to be adjusted by 1 min 30 sec - rounded to the next 15 min
+        assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(15),
+                123 + TimeUnit.HOURS.toMillis(2),
+                Instant.parse("2007-12-03T10:13:30.00Z").toEpochMilli())).isEqualTo(
+                123 + Duration.parse("PT1M30S").toMillis());
+
+        // Expect the aligned value to be adjusted by 2 min 45 sec - rounded to the next 15 min
+        assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(15),
+                123 + TimeUnit.HOURS.toMillis(2),
+                Instant.parse("2007-12-03T10:57:15.00Z").toEpochMilli())).isEqualTo(
+                123 + Duration.parse("PT2M45S").toMillis());
+
+        // Expect the aligned value to be adjusted by 15 sec - rounded to the next 1 min
+        assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(1),
+                123 + TimeUnit.HOURS.toMillis(2),
+                Instant.parse("2007-12-03T10:14:45.00Z").toEpochMilli())).isEqualTo(
+                123 + Duration.parse("PT15S").toMillis());
+
+        // Expect the aligned value to be adjusted by 1 hour 46 min 30 sec -
+        // rounded to the next 3 hours
+        assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.HOURS.toMillis(3),
+                123 + TimeUnit.HOURS.toMillis(9),
+                Instant.parse("2007-12-03T10:13:30.00Z").toEpochMilli())).isEqualTo(
+                123 + Duration.parse("PT1H46M30S").toMillis());
+    }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java
new file mode 100644
index 0000000..d3628b5
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SuppressWarnings("GuardedBy")
+public class PowerStatsStoreTest {
+    private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024;
+
+    private PowerStatsStore mPowerStatsStore;
+    private File mStoreDirectory;
+
+    @Before
+    public void setup() {
+        Context context = InstrumentationRegistry.getContext();
+
+        mStoreDirectory = new File(context.getCacheDir(), "PowerStatsStoreTest");
+        clearDirectory(mStoreDirectory);
+
+        mPowerStatsStore = new PowerStatsStore(mStoreDirectory,
+                MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES,
+                new TestHandler(),
+                (sectionType, parser) -> {
+                    if (sectionType.equals(TestSection.TYPE)) {
+                        return TestSection.readXml(parser);
+                    }
+                    return null;
+                });
+    }
+
+    @Test
+    public void garbageCollectOldSpans() throws Exception {
+        int spanSize = 500;
+        final int numberOfSnaps =
+                (int) (MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES / spanSize);
+        for (int i = 0; i < numberOfSnaps + 2; i++) {
+            PowerStatsSpan span = new PowerStatsSpan(i);
+            span.addSection(new TestSection(i, spanSize));
+            mPowerStatsStore.storePowerStatsSpan(span);
+        }
+
+        assertThat(getDirectorySize(mStoreDirectory))
+                .isAtMost(MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
+
+        List<PowerStatsSpan.Metadata> toc = mPowerStatsStore.getTableOfContents();
+        assertThat(toc.size()).isLessThan(numberOfSnaps);
+        int minPreservedSpanId = numberOfSnaps - toc.size();
+        for (PowerStatsSpan.Metadata metadata : toc) {
+            assertThat(metadata.getId()).isAtLeast(minPreservedSpanId);
+        }
+    }
+
+    @Test
+    public void reset() throws Exception {
+        for (int i = 0; i < 3; i++) {
+            PowerStatsSpan span = new PowerStatsSpan(i);
+            span.addSection(new TestSection(i, 42));
+            mPowerStatsStore.storePowerStatsSpan(span);
+        }
+
+        assertThat(getDirectorySize(mStoreDirectory)).isNotEqualTo(0);
+
+        mPowerStatsStore.reset();
+
+        assertThat(getDirectorySize(mStoreDirectory)).isEqualTo(0);
+    }
+
+    private void clearDirectory(File dir) {
+        if (dir.exists()) {
+            for (File child : dir.listFiles()) {
+                if (child.isDirectory()) {
+                    clearDirectory(child);
+                }
+                child.delete();
+            }
+        }
+    }
+
+    private long getDirectorySize(File dir) {
+        long size = 0;
+        if (dir.exists()) {
+            for (File child : dir.listFiles()) {
+                if (child.isDirectory()) {
+                    size += getDirectorySize(child);
+                } else {
+                    size += child.length();
+                }
+            }
+        }
+        return size;
+    }
+
+    private static class TestSection extends PowerStatsSpan.Section {
+        public static final String TYPE = "much-text";
+
+        private final int mSize;
+        private final int mValue;
+
+        TestSection(int value, int size) {
+            super(TYPE);
+            mSize = size;
+            mValue = value;
+        }
+
+        @Override
+        void write(TypedXmlSerializer serializer) throws IOException {
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < mSize; i++) {
+                sb.append("X");
+            }
+            serializer.startTag(null, "much-text");
+            serializer.attributeInt(null, "value", mValue);
+            serializer.text(sb.toString());
+            serializer.endTag(null, "much-text");
+        }
+
+        public static TestSection readXml(TypedXmlPullParser parser) throws XmlPullParserException {
+            TestSection section = null;
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.END_DOCUMENT
+                   && !(eventType == XmlPullParser.END_TAG
+                        && parser.getName().equals("much-text"))) {
+                if (eventType == XmlPullParser.START_TAG && parser.getName().equals("much-text")) {
+                    section = new TestSection(parser.getAttributeInt(null, "value"), 0);
+                }
+            }
+            return section;
+        }
+    }
+
+    private static class TestHandler extends Handler {
+        TestHandler() {
+            super(Looper.getMainLooper());
+        }
+
+        @Override
+        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+            msg.getCallback().run();
+            return true;
+        }
+    }
+}
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/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index 0115db6..ef19ba1 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -40,6 +40,7 @@
             mediaSharedWithParent='true'
             credentialShareableWithParent='false'
             showInSettings='23'
+            hideInSettingsInQuietMode='true'
             inheritDevicePolicy='450'
             deleteAppWithParent='false'
             alwaysVisible='true'
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/FlashNotificationsControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java
index 8a057df..0988eea 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java
@@ -366,6 +366,7 @@
     private void assumeCameraTorchAvailable() {
         assumeTrue(mCameraManager != null);
         assumeTrue(!mCameraInfoMap.isEmpty());
+        assumeTrue(mCameraInfoMap.values().stream().anyMatch(info -> info.mIsValid));
     }
 
     private void simulateCallSequence() throws InterruptedException {
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/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 430f600..caa9e7c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -1065,7 +1065,7 @@
                     mMgh.clearAndTransitionToStateDetecting();
                     break;
                 case STATE_ACTIVATED:
-                    if (mMgh.mDetectTripleTap) {
+                    if (mMgh.mDetectSingleFingerTripleTap) {
                         goFromStateIdleTo(STATE_2TAPS);
                         tap();
                     } else {
@@ -1147,7 +1147,7 @@
                 break;
             case STATE_ACTIVATED:
             case STATE_ZOOMED_OUT_FROM_SERVICE:
-                if (mMgh.mDetectTripleTap) {
+                if (mMgh.mDetectSingleFingerTripleTap) {
                     tap();
                     tap();
                     returnToNormalFrom(STATE_ACTIVATED_2TAPS);
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/am/BatteryStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
index 988cd81..feb6bd9 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
@@ -34,6 +36,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.File;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -49,8 +52,9 @@
         final Context context = InstrumentationRegistry.getContext();
         mBgThread = new HandlerThread("bg thread");
         mBgThread.start();
-        mBatteryStatsService = new BatteryStatsService(context,
-                context.getCacheDir(), new Handler(mBgThread.getLooper()));
+        File systemDir = context.getCacheDir();
+        Handler handler = new Handler(mBgThread.getLooper());
+        mBatteryStatsService = new BatteryStatsService(context, systemDir, handler);
     }
 
     @After
@@ -121,4 +125,14 @@
             waitThread.join(1000);
         }
     }
+
+    @Test
+    public void testSavingStatsdAtomPullTimestamp() {
+        mBatteryStatsService.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(1234);
+        assertThat(mBatteryStatsService.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
+                .isEqualTo(1234);
+        mBatteryStatsService.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(5478);
+        assertThat(mBatteryStatsService.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
+                .isEqualTo(5478);
+    }
 }
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/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 61b30a0..e8cbcf9 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -233,6 +233,7 @@
     private VirtualDeviceManagerService mVdms;
     private VirtualDeviceManagerInternal mLocalService;
     private VirtualDeviceManagerService.VirtualDeviceManagerImpl mVdm;
+    private VirtualDeviceManagerService.VirtualDeviceManagerNativeImpl mVdmNative;
     private VirtualDeviceLog mVirtualDeviceLog;
     @Mock
     private InputController.NativeWrapper mNativeWrapperMock;
@@ -340,6 +341,7 @@
         mSetFlagsRule.disableFlags(Flags.FLAG_DYNAMIC_POLICY);
         mSetFlagsRule.disableFlags(Flags.FLAG_STREAM_PERMISSIONS);
         mSetFlagsRule.disableFlags(Flags.FLAG_VDM_CUSTOM_HOME);
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_NATIVE_VDM);
 
         doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
         doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt());
@@ -384,6 +386,7 @@
         mVdms = new VirtualDeviceManagerService(mContext);
         mLocalService = mVdms.getLocalServiceInstance();
         mVdm = mVdms.new VirtualDeviceManagerImpl();
+        mVdmNative = mVdms.new VirtualDeviceManagerNativeImpl();
         mVirtualDeviceLog = new VirtualDeviceLog(mContext);
         mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1);
         mSensorController = mDeviceImpl.getSensorControllerForTest();
@@ -440,24 +443,32 @@
     public void getDevicePolicy_invalidDeviceId_returnsDefault() {
         assertThat(mVdm.getDevicePolicy(DEVICE_ID_INVALID, POLICY_TYPE_SENSORS))
                 .isEqualTo(DEVICE_POLICY_DEFAULT);
+        assertThat(mVdmNative.getDevicePolicy(DEVICE_ID_INVALID, POLICY_TYPE_SENSORS))
+                .isEqualTo(DEVICE_POLICY_DEFAULT);
     }
 
     @Test
     public void getDevicePolicy_defaultDeviceId_returnsDefault() {
         assertThat(mVdm.getDevicePolicy(DEVICE_ID_DEFAULT, POLICY_TYPE_SENSORS))
                 .isEqualTo(DEVICE_POLICY_DEFAULT);
+        assertThat(mVdmNative.getDevicePolicy(DEVICE_ID_DEFAULT, POLICY_TYPE_SENSORS))
+                .isEqualTo(DEVICE_POLICY_DEFAULT);
     }
 
     @Test
     public void getDevicePolicy_nonExistentDeviceId_returnsDefault() {
         assertThat(mVdm.getDevicePolicy(mDeviceImpl.getDeviceId() + 1, POLICY_TYPE_SENSORS))
                 .isEqualTo(DEVICE_POLICY_DEFAULT);
+        assertThat(mVdmNative.getDevicePolicy(mDeviceImpl.getDeviceId() + 1, POLICY_TYPE_SENSORS))
+                .isEqualTo(DEVICE_POLICY_DEFAULT);
     }
 
     @Test
     public void getDevicePolicy_unspecifiedPolicy_returnsDefault() {
         assertThat(mVdm.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
                 .isEqualTo(DEVICE_POLICY_DEFAULT);
+        assertThat(mVdmNative.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
+                .isEqualTo(DEVICE_POLICY_DEFAULT);
     }
 
     @Test
@@ -472,6 +483,8 @@
 
         assertThat(mVdm.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
                 .isEqualTo(DEVICE_POLICY_CUSTOM);
+        assertThat(mVdmNative.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
+                .isEqualTo(DEVICE_POLICY_CUSTOM);
     }
 
     @Test
@@ -567,8 +580,8 @@
 
     @Test
     public void getDeviceIdsForUid_noRunningApps_returnsNull() {
-        Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
-        assertThat(deviceIds).isEmpty();
+        assertThat(mLocalService.getDeviceIdsForUid(UID_1)).isEmpty();
+        assertThat(mVdmNative.getDeviceIdsForUid(UID_1)).isEmpty();
     }
 
     @Test
@@ -577,8 +590,8 @@
         mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
                 Sets.newArraySet(UID_2));
 
-        Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
-        assertThat(deviceIds).isEmpty();
+        assertThat(mLocalService.getDeviceIdsForUid(UID_1)).isEmpty();
+        assertThat(mVdmNative.getDeviceIdsForUid(UID_1)).isEmpty();
     }
 
     @Test
@@ -587,8 +600,9 @@
         mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
                 Sets.newArraySet(UID_1));
 
-        Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
-        assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId());
+        int deviceId = mDeviceImpl.getDeviceId();
+        assertThat(mLocalService.getDeviceIdsForUid(UID_1)).containsExactly(deviceId);
+        assertThat(mVdmNative.getDeviceIdsForUid(UID_1)).asList().containsExactly(deviceId);
     }
 
     @Test
@@ -598,8 +612,9 @@
         mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
                 Sets.newArraySet(UID_1, UID_2));
 
-        Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
-        assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId());
+        int deviceId = mDeviceImpl.getDeviceId();
+        assertThat(mLocalService.getDeviceIdsForUid(UID_1)).containsExactly(deviceId);
+        assertThat(mVdmNative.getDeviceIdsForUid(UID_1)).asList().containsExactly(deviceId);
     }
 
     @Test
@@ -611,8 +626,9 @@
         secondDevice.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_2).onRunningAppsChanged(
                 Sets.newArraySet(UID_1));
 
-        Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
-        assertThat(deviceIds).containsExactly(secondDevice.getDeviceId());
+        int deviceId = secondDevice.getDeviceId();
+        assertThat(mLocalService.getDeviceIdsForUid(UID_1)).containsExactly(deviceId);
+        assertThat(mVdmNative.getDeviceIdsForUid(UID_1)).asList().containsExactly(deviceId);
     }
 
     @Test
@@ -628,8 +644,9 @@
         secondDevice.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_2).onRunningAppsChanged(
                 Sets.newArraySet(UID_1, UID_2));
 
-        Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
-        assertThat(deviceIds).containsExactly(
+        assertThat(mLocalService.getDeviceIdsForUid(UID_1)).containsExactly(
+                mDeviceImpl.getDeviceId(), secondDevice.getDeviceId());
+        assertThat(mVdmNative.getDeviceIdsForUid(UID_1)).asList().containsExactly(
                 mDeviceImpl.getDeviceId(), secondDevice.getDeviceId());
     }
 
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/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 7d73563..7dcfc88 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -284,6 +284,7 @@
         assertEquals(info.currentState, DEFAULT_DEVICE_STATE.getIdentifier());
     }
 
+    @FlakyTest(bugId = 297949293)
     @Test
     public void getDeviceStateInfo_baseStateAndCommittedStateNotSet() throws RemoteException {
         // Create a provider and a service without an initial base state.
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 184c976..3de167e 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -336,7 +336,8 @@
             return new FontConfig(Collections.emptyList(),
                     Collections.emptyList(),
                     Collections.singletonList(new FontConfig.NamedFamilyList(
-                            Collections.singletonList(family), "sans-serif")), 0, 1);
+                            Collections.singletonList(family), "sans-serif")),
+                    Collections.emptyList(), 0, 1);
         };
 
         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
@@ -499,7 +500,8 @@
                     Collections.emptyList(),
                     Collections.emptyList(),
                     Collections.singletonList(new FontConfig.NamedFamilyList(
-                            Collections.singletonList(family), "sans-serif")), 0, 1);
+                            Collections.singletonList(family), "sans-serif")),
+                    Collections.emptyList(), 0, 1);
         });
         dir.loadFontFileMap();
 
@@ -651,7 +653,8 @@
                     FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
             return new FontConfig(Collections.emptyList(), Collections.emptyList(),
                     Collections.singletonList(new FontConfig.NamedFamilyList(
-                            Collections.singletonList(family), "sans-serif")), 0, 1);
+                            Collections.singletonList(family), "sans-serif")),
+                    Collections.emptyList(), 0, 1);
         });
         dir.loadFontFileMap();
 
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/BluetoothRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
index 75d71da..06f117b 100644
--- a/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
@@ -38,9 +38,10 @@
 public class BluetoothRouteControllerTest {
 
     private final BluetoothRouteController.BluetoothRoutesUpdatedListener
-            mBluetoothRoutesUpdatedListener = routes -> {
-                // Empty on purpose.
-            };
+            mBluetoothRoutesUpdatedListener =
+                    () -> {
+                        // Empty on purpose.
+                    };
 
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
index ec4b8a8..14b121d 100644
--- a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
@@ -38,7 +38,7 @@
 public class DeviceRouteControllerTest {
 
     private final DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener =
-            deviceRoute -> {
+            () -> {
                 // Empty on purpose.
             };
 
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/net/LockdownVpnTrackerTest.java b/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java
new file mode 100644
index 0000000..949f8e7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java
@@ -0,0 +1,335 @@
+/*
+ * 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.net;
+
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.text.TextUtils;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
+import com.android.server.connectivity.Vpn;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LockdownVpnTrackerTest {
+    private static final NetworkCapabilities TEST_CELL_NC = new NetworkCapabilities.Builder()
+            .addTransportType(TRANSPORT_CELLULAR)
+            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+            .build();
+    private static final LinkProperties TEST_CELL_LP = new LinkProperties();
+
+    static {
+        TEST_CELL_LP.setInterfaceName("rmnet0");
+        TEST_CELL_LP.addLinkAddress(new LinkAddress("192.0.2.2/25"));
+    }
+
+    // Use a context wrapper instead of a mock since LockdownVpnTracker builds notifications which
+    // is tedious and currently unnecessary to mock.
+    private final Context mContext = new ContextWrapper(InstrumentationRegistry.getContext()) {
+        @Override
+        public Object getSystemService(String name) {
+            if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
+            if (Context.NOTIFICATION_SERVICE.equals(name)) return mNotificationManager;
+
+            return super.getSystemService(name);
+        }
+    };
+    @Mock private ConnectivityManager mCm;
+    @Mock private Vpn mVpn;
+    @Mock private NotificationManager mNotificationManager;
+    @Mock private NetworkInfo mVpnNetworkInfo;
+    @Mock private VpnConfig mVpnConfig;
+    @Mock private Network mNetwork;
+    @Mock private Network mNetwork2;
+    @Mock private Network mVpnNetwork;
+
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
+    private VpnProfile mProfile;
+
+    private VpnProfile createTestVpnProfile() {
+        final String profileName = "testVpnProfile";
+        final VpnProfile profile = new VpnProfile(profileName);
+        profile.name = "My VPN";
+        profile.server = "192.0.2.1";
+        profile.dnsServers = "8.8.8.8";
+        profile.ipsecIdentifier = "My ipsecIdentifier";
+        profile.ipsecSecret = "My PSK";
+        profile.type = VpnProfile.TYPE_IKEV2_IPSEC_PSK;
+
+        return profile;
+    }
+
+    private NetworkCallback getDefaultNetworkCallback() {
+        final ArgumentCaptor<NetworkCallback> callbackCaptor =
+                ArgumentCaptor.forClass(NetworkCallback.class);
+        verify(mCm).registerSystemDefaultNetworkCallback(callbackCaptor.capture(), eq(mHandler));
+        return callbackCaptor.getValue();
+    }
+
+    private NetworkCallback getVpnNetworkCallback() {
+        final ArgumentCaptor<NetworkCallback> callbackCaptor =
+                ArgumentCaptor.forClass(NetworkCallback.class);
+        verify(mCm).registerNetworkCallback(any(), callbackCaptor.capture(), eq(mHandler));
+        return callbackCaptor.getValue();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mHandlerThread = new HandlerThread("LockdownVpnTrackerTest");
+        mHandlerThread.start();
+        mHandler = mHandlerThread.getThreadHandler();
+
+        doReturn(mVpnNetworkInfo).when(mVpn).getNetworkInfo();
+        doReturn(false).when(mVpnNetworkInfo).isConnectedOrConnecting();
+        doReturn(mVpnConfig).when(mVpn).getLegacyVpnConfig();
+        // mVpnConfig is a mock but the production code will try to add addresses in this array
+        // assuming it's non-null, so it needs to be initialized.
+        mVpnConfig.addresses = new ArrayList<>();
+
+        mProfile = createTestVpnProfile();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mHandlerThread != null) {
+            mHandlerThread.quitSafely();
+            mHandlerThread.join();
+        }
+    }
+
+    private LockdownVpnTracker initAndVerifyLockdownVpnTracker() {
+        final LockdownVpnTracker lockdownVpnTracker =
+                new LockdownVpnTracker(mContext, mHandler, mVpn, mProfile);
+        lockdownVpnTracker.init();
+        verify(mVpn).setEnableTeardown(false);
+        verify(mVpn).setLockdown(true);
+        verify(mCm).setLegacyLockdownVpnEnabled(true);
+        verify(mVpn).stopVpnRunnerPrivileged();
+        verify(mNotificationManager).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS));
+
+        return lockdownVpnTracker;
+    }
+
+    private void callCallbacksForNetworkConnect(NetworkCallback callback, Network network,
+            NetworkCapabilities nc, LinkProperties lp, boolean blocked) {
+        callback.onAvailable(network);
+        callback.onCapabilitiesChanged(network, nc);
+        callback.onLinkPropertiesChanged(network, lp);
+        callback.onBlockedStatusChanged(network, blocked);
+    }
+
+    private void callCallbacksForNetworkConnect(NetworkCallback callback, Network network) {
+        callCallbacksForNetworkConnect(
+                callback, network, TEST_CELL_NC, TEST_CELL_LP, true /* blocked */);
+    }
+
+    private boolean isExpectedNotification(Notification notification, int titleRes, int iconRes) {
+        if (!NOTIFICATION_CHANNEL_VPN.equals(notification.getChannelId())) {
+            return false;
+        }
+        final CharSequence expectedTitle = mContext.getString(titleRes);
+        final CharSequence actualTitle = notification.extras.getCharSequence(
+                Notification.EXTRA_TITLE);
+        if (!TextUtils.equals(expectedTitle, actualTitle)) {
+            return false;
+        }
+        return notification.getSmallIcon().getResId() == iconRes;
+    }
+
+    @Test
+    public void testShutdown() {
+        final LockdownVpnTracker lockdownVpnTracker = initAndVerifyLockdownVpnTracker();
+        final NetworkCallback defaultCallback = getDefaultNetworkCallback();
+        final NetworkCallback vpnCallback = getVpnNetworkCallback();
+        clearInvocations(mVpn, mCm, mNotificationManager);
+
+        lockdownVpnTracker.shutdown();
+        verify(mVpn).stopVpnRunnerPrivileged();
+        verify(mVpn).setLockdown(false);
+        verify(mCm).setLegacyLockdownVpnEnabled(false);
+        verify(mNotificationManager).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS));
+        verify(mVpn).setEnableTeardown(true);
+        verify(mCm).unregisterNetworkCallback(defaultCallback);
+        verify(mCm).unregisterNetworkCallback(vpnCallback);
+    }
+
+    @Test
+    public void testDefaultNetworkConnected() {
+        initAndVerifyLockdownVpnTracker();
+        final NetworkCallback defaultCallback = getDefaultNetworkCallback();
+        clearInvocations(mVpn, mCm, mNotificationManager);
+
+        // mNetwork connected and available.
+        callCallbacksForNetworkConnect(defaultCallback, mNetwork);
+
+        // Vpn is starting
+        verify(mVpn).startLegacyVpnPrivileged(mProfile, mNetwork, TEST_CELL_LP);
+        verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS),
+                argThat(notification -> isExpectedNotification(notification,
+                        R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected)));
+    }
+
+    private void doTestDefaultLpChanged(LinkProperties startingLp, LinkProperties newLp) {
+        initAndVerifyLockdownVpnTracker();
+        final NetworkCallback defaultCallback = getDefaultNetworkCallback();
+        callCallbacksForNetworkConnect(
+                defaultCallback, mNetwork, TEST_CELL_NC, startingLp, true /* blocked */);
+        clearInvocations(mVpn, mCm, mNotificationManager);
+
+        // LockdownVpnTracker#handleStateChangedLocked() is not called on the same network even if
+        // the LinkProperties change.
+        defaultCallback.onLinkPropertiesChanged(mNetwork, newLp);
+
+        // Ideally the VPN should start if it hasn't already, but it doesn't because nothing calls
+        // LockdownVpnTracker#handleStateChangedLocked. This is a bug.
+        // TODO: consider fixing this.
+        verify(mVpn, never()).stopVpnRunnerPrivileged();
+        verify(mVpn, never()).startLegacyVpnPrivileged(any(), any(), any());
+        verify(mNotificationManager, never()).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS));
+    }
+
+    @Test
+    public void testDefaultLPChanged_V4AddLinkAddressV4() {
+        final LinkProperties lp = new LinkProperties(TEST_CELL_LP);
+        lp.setInterfaceName("rmnet0");
+        lp.addLinkAddress(new LinkAddress("192.0.2.3/25"));
+        doTestDefaultLpChanged(TEST_CELL_LP, lp);
+    }
+
+    @Test
+    public void testDefaultLPChanged_V4AddLinkAddressV6() {
+        final LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName("rmnet0");
+        lp.addLinkAddress(new LinkAddress("192.0.2.3/25"));
+        final LinkProperties newLp = new LinkProperties(lp);
+        newLp.addLinkAddress(new LinkAddress("2001:db8::1/64"));
+        doTestDefaultLpChanged(lp, newLp);
+    }
+
+    @Test
+    public void testDefaultLPChanged_V6AddLinkAddressV4() {
+        final LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName("rmnet0");
+        lp.addLinkAddress(new LinkAddress("2001:db8::1/64"));
+        final LinkProperties newLp = new LinkProperties(lp);
+        newLp.addLinkAddress(new LinkAddress("192.0.2.3/25"));
+        doTestDefaultLpChanged(lp, newLp);
+    }
+
+    @Test
+    public void testDefaultLPChanged_AddLinkAddressV4() {
+        final LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName("rmnet0");
+        doTestDefaultLpChanged(lp, TEST_CELL_LP);
+    }
+
+    @Test
+    public void testDefaultNetworkChanged() {
+        initAndVerifyLockdownVpnTracker();
+        final NetworkCallback defaultCallback = getDefaultNetworkCallback();
+        final NetworkCallback vpnCallback = getVpnNetworkCallback();
+        callCallbacksForNetworkConnect(defaultCallback, mNetwork);
+        clearInvocations(mVpn, mCm, mNotificationManager);
+
+        // New network and LinkProperties received
+        final NetworkCapabilities wifiNc = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_WIFI)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+                .build();
+        final LinkProperties wifiLp = new LinkProperties();
+        wifiLp.setInterfaceName("wlan0");
+        callCallbacksForNetworkConnect(
+                defaultCallback, mNetwork2, wifiNc, wifiLp, true /* blocked */);
+
+        // Vpn is restarted.
+        verify(mVpn).stopVpnRunnerPrivileged();
+        verify(mVpn).startLegacyVpnPrivileged(mProfile, mNetwork2, wifiLp);
+        verify(mNotificationManager, never()).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS));
+        verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS),
+                argThat(notification -> isExpectedNotification(notification,
+                        R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected)));
+
+        // Vpn is Connected
+        doReturn(true).when(mVpnNetworkInfo).isConnectedOrConnecting();
+        doReturn(true).when(mVpnNetworkInfo).isConnected();
+        vpnCallback.onAvailable(mVpnNetwork);
+        verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS),
+                argThat(notification -> isExpectedNotification(notification,
+                        R.string.vpn_lockdown_connected, R.drawable.vpn_connected)));
+
+    }
+
+    @Test
+    public void testSystemDefaultLost() {
+        initAndVerifyLockdownVpnTracker();
+        final NetworkCallback defaultCallback = getDefaultNetworkCallback();
+        // mNetwork connected
+        callCallbacksForNetworkConnect(defaultCallback, mNetwork);
+        clearInvocations(mVpn, mCm, mNotificationManager);
+
+        defaultCallback.onLost(mNetwork);
+
+        // Vpn is stopped
+        verify(mVpn).stopVpnRunnerPrivileged();
+        verify(mNotificationManager).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS));
+    }
+}
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/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
index 9f75cf8..253592c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -43,6 +43,7 @@
 import android.app.PropertyInvalidatedCache;
 import android.content.pm.UserInfo;
 import android.content.pm.UserInfo.UserInfoFlag;
+import android.multiuser.Flags;
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.UserHandle;
@@ -124,18 +125,34 @@
 
         mUserManagerService.putUserInfo(data.info);
 
-        // Set a global and user restriction so they get written out to the user file.
+        //Local restrictions are written to the user specific files and global restrictions
+        // are written to the SYSTEM user file.
         setUserRestrictions(data.info.id, globalRestriction, localRestriction, true);
 
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         DataOutputStream out = new DataOutputStream(baos);
         mUserManagerService.writeUserLP(data, out);
-        byte[] bytes = baos.toByteArray();
+        byte[] secondaryUserBytes = baos.toByteArray();
+        baos.reset();
+
+        byte[] systemUserBytes = new byte[0];
+        if (Flags.saveGlobalAndGuestRestrictionsOnSystemUserXmlReadOnly()) {
+            UserData systemUserData = new UserData();
+            systemUserData.info = mUserManagerService.getUserInfo(UserHandle.USER_SYSTEM);
+            mUserManagerService.writeUserLP(systemUserData, baos);
+            systemUserBytes = baos.toByteArray();
+        }
 
         // Clear the restrictions to see if they are properly read in from the user file.
         setUserRestrictions(data.info.id, globalRestriction, localRestriction, false);
 
-        mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(bytes));
+        //read the secondary and SYSTEM user file to fetch local/global device policy restrictions.
+        mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(secondaryUserBytes));
+        if (Flags.saveGlobalAndGuestRestrictionsOnSystemUserXmlReadOnly()) {
+            mUserManagerService.readUserLP(UserHandle.USER_SYSTEM,
+                    new ByteArrayInputStream(systemUserBytes));
+        }
+
         assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(globalRestriction));
         assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(localRestriction));
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index a54bc91..c684a7b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -60,6 +60,7 @@
                 .setShowInLauncher(21)
                 .setStartWithParent(false)
                 .setShowInSettings(45)
+                .setHideInSettingsInQuietMode(false)
                 .setInheritDevicePolicy(67)
                 .setUseParentsContacts(false)
                 .setCrossProfileIntentFilterAccessControl(10)
@@ -72,6 +73,7 @@
         final UserProperties actualProps = new UserProperties(defaultProps);
         actualProps.setShowInLauncher(14);
         actualProps.setShowInSettings(32);
+        actualProps.setHideInSettingsInQuietMode(true);
         actualProps.setInheritDevicePolicy(51);
         actualProps.setUseParentsContacts(true);
         actualProps.setCrossProfileIntentFilterAccessControl(20);
@@ -228,6 +230,8 @@
         assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher());
         assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent());
         assertThat(expected.getShowInSettings()).isEqualTo(actual.getShowInSettings());
+        assertThat(expected.getHideInSettingsInQuietMode())
+                .isEqualTo(actual.getHideInSettingsInQuietMode());
         assertThat(expected.getInheritDevicePolicy()).isEqualTo(actual.getInheritDevicePolicy());
         assertThat(expected.getUseParentsContacts()).isEqualTo(actual.getUseParentsContacts());
         assertThat(expected.getCrossProfileIntentFilterAccessControl())
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index e3579b4..20270a8 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -90,6 +90,7 @@
                 .setMediaSharedWithParent(true)
                 .setCredentialShareableWithParent(false)
                 .setShowInSettings(900)
+                .setHideInSettingsInQuietMode(true)
                 .setInheritDevicePolicy(340)
                 .setDeleteAppWithParent(true)
                 .setAlwaysVisible(true);
@@ -160,6 +161,7 @@
         assertTrue(type.getDefaultUserPropertiesReference().isMediaSharedWithParent());
         assertFalse(type.getDefaultUserPropertiesReference().isCredentialShareableWithParent());
         assertEquals(900, type.getDefaultUserPropertiesReference().getShowInSettings());
+        assertTrue(type.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode());
         assertEquals(340, type.getDefaultUserPropertiesReference()
                 .getInheritDevicePolicy());
         assertTrue(type.getDefaultUserPropertiesReference().getDeleteAppWithParent());
@@ -217,6 +219,7 @@
         assertFalse(props.isCredentialShareableWithParent());
         assertFalse(props.getDeleteAppWithParent());
         assertFalse(props.getAlwaysVisible());
+        assertFalse(props.getHideInSettingsInQuietMode());
 
         assertFalse(type.hasBadge());
     }
@@ -304,6 +307,7 @@
                 .setMediaSharedWithParent(false)
                 .setCredentialShareableWithParent(true)
                 .setShowInSettings(20)
+                .setHideInSettingsInQuietMode(false)
                 .setInheritDevicePolicy(21)
                 .setDeleteAppWithParent(true)
                 .setAlwaysVisible(false);
@@ -344,6 +348,7 @@
         assertTrue(aospType.getDefaultUserPropertiesReference()
                 .isCredentialShareableWithParent());
         assertEquals(20, aospType.getDefaultUserPropertiesReference().getShowInSettings());
+        assertFalse(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode());
         assertEquals(21, aospType.getDefaultUserPropertiesReference()
                 .getInheritDevicePolicy());
         assertTrue(aospType.getDefaultUserPropertiesReference().getDeleteAppWithParent());
@@ -390,6 +395,7 @@
         assertFalse(aospType.getDefaultUserPropertiesReference()
                 .isCredentialShareableWithParent());
         assertEquals(23, aospType.getDefaultUserPropertiesReference().getShowInSettings());
+        assertTrue(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode());
         assertEquals(450, aospType.getDefaultUserPropertiesReference()
                 .getInheritDevicePolicy());
         assertFalse(aospType.getDefaultUserPropertiesReference().getDeleteAppWithParent());
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index d1f4961..8891413 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -43,6 +43,8 @@
         // TODO: remove once Android migrates to JUnit 4.12,
         // which provides assertThrows
         "testng",
+        "flag-junit",
+        "notification_flags_lib",
     ],
 
     libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 9742384..42ad73a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -32,6 +32,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
@@ -147,6 +148,7 @@
     private static final int CUSTOM_LIGHT_ON = 10000;
     private static final int CUSTOM_LIGHT_OFF = 10000;
     private static final int MAX_VIBRATION_DELAY = 1000;
+    private static final float DEFAULT_VOLUME = 1.0f;
 
     @Before
     public void setUp() throws Exception {
@@ -397,19 +399,22 @@
     //
 
     private void verifyNeverBeep() throws RemoteException {
-        verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any());
+        verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any(), anyFloat());
     }
 
     private void verifyBeepUnlooped() throws RemoteException  {
-        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any());
+        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any(),
+                eq(DEFAULT_VOLUME));
     }
 
     private void verifyBeepLooped() throws RemoteException  {
-        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any());
+        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any(),
+                eq(DEFAULT_VOLUME));
     }
 
     private void verifyBeep(int times)  throws RemoteException  {
-        verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any());
+        verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any(),
+                eq(DEFAULT_VOLUME));
     }
 
     private void verifyNeverStopAudio() throws RemoteException {
@@ -905,7 +910,7 @@
         verifyDelayedVibrate(
                 mService.getVibratorHelper().createFallbackVibration(/* insistent= */ false));
         verify(mRingtonePlayer, never()).playAsync
-                (anyObject(), anyObject(), anyBoolean(), anyObject());
+                (anyObject(), anyObject(), anyBoolean(), anyObject(), anyFloat());
         assertTrue(r.isInterruptive());
         assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 81867df..9bd938f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -57,6 +57,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.graphics.Color;
 import android.graphics.drawable.Icon;
 import android.media.AudioAttributes;
@@ -66,9 +67,11 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
@@ -87,8 +90,11 @@
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
 import com.android.server.pm.PackageManagerService;
+
+import java.util.List;
 import java.util.Objects;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -102,6 +108,8 @@
 @RunWith(AndroidJUnit4.class)
 @SuppressLint("GuardedBy")
 public class NotificationAttentionHelperTest extends UiServiceTestCase {
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Mock AudioManager mAudioManager;
     @Mock Vibrator mVibrator;
@@ -115,6 +123,8 @@
     IAccessibilityManager mAccessibilityService;
     @Mock
     KeyguardManager mKeyguardManager;
+    @Mock
+    private UserManager mUserManager;
     NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
     private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
         1 << 30);
@@ -134,6 +144,8 @@
     private AccessibilityManager mAccessibilityManager;
     private static final NotificationAttentionHelper.Signals DEFAULT_SIGNALS =
         new NotificationAttentionHelper.Signals(false, 0);
+    private static final NotificationAttentionHelper.Signals WORK_PROFILE_SIGNALS =
+            new NotificationAttentionHelper.Signals(true, 0);
 
     private VibrateRepeatMatcher mVibrateOnceMatcher = new VibrateRepeatMatcher(-1);
     private VibrateRepeatMatcher mVibrateLoopMatcher = new VibrateRepeatMatcher(0);
@@ -151,6 +163,7 @@
     private static final int CUSTOM_LIGHT_ON = 10000;
     private static final int CUSTOM_LIGHT_OFF = 10000;
     private static final int MAX_VIBRATION_DELAY = 1000;
+    private static final float DEFAULT_VOLUME = 1.0f;
 
     @Before
     public void setUp() throws Exception {
@@ -178,6 +191,8 @@
 
         // TODO (b/291907312): remove feature flag
         mTestFlagResolver.setFlagOverride(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR, true);
+        // Disable feature flags by default. Tests should enable as needed.
+        mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_EXPIRE_BITMAPS);
 
         mService = spy(new NotificationManagerService(getContext(), mNotificationRecordLogger,
             mNotificationInstanceIdSequence));
@@ -189,9 +204,9 @@
 
     private void initAttentionHelper(TestableFlagResolver flagResolver) {
         mAttentionHelper = new NotificationAttentionHelper(getContext(), mock(LightsManager.class),
-            mAccessibilityManager, getContext().getPackageManager(), mUsageStats,
+            mAccessibilityManager, getContext().getPackageManager(), mUserManager, mUsageStats,
             mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver);
-        mAttentionHelper.setVibratorHelper(new VibratorHelper(getContext()));
+        mAttentionHelper.setVibratorHelper(spy(new VibratorHelper(getContext())));
         mAttentionHelper.setAudioManager(mAudioManager);
         mAttentionHelper.setSystemReady(true);
         mAttentionHelper.setLights(mLight);
@@ -282,6 +297,11 @@
             true /* noisy */, true /* buzzy*/, false /* lights */);
     }
 
+    private NotificationRecord getBuzzyBeepyNotification(UserHandle userHandle) {
+        return getNotificationRecord(mId, false /* insistent */, false /* once */,
+                true /* noisy */, true /* buzzy*/, false /* lights */, userHandle);
+    }
+
     private NotificationRecord getLightsNotification() {
         return getNotificationRecord(mId, false /* insistent */, false /* once */,
             false /* noisy */, false /* buzzy*/, true /* lights */);
@@ -312,7 +332,13 @@
     private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
         boolean noisy, boolean buzzy, boolean lights) {
         return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, buzzy, noisy,
-            lights, null, Notification.GROUP_ALERT_ALL, false);
+            lights, null, Notification.GROUP_ALERT_ALL, false, mUser);
+    }
+
+    private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
+            boolean noisy, boolean buzzy, boolean lights, UserHandle userHandle) {
+        return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, buzzy, noisy,
+                lights, null, Notification.GROUP_ALERT_ALL, false, userHandle);
     }
 
     private NotificationRecord getLeanbackNotificationRecord(int id, boolean insistent,
@@ -320,25 +346,25 @@
         boolean noisy, boolean buzzy, boolean lights) {
         return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true,
             true,
-            null, Notification.GROUP_ALERT_ALL, true);
+            null, Notification.GROUP_ALERT_ALL, true, mUser);
     }
 
     private NotificationRecord getBeepyNotificationRecord(String groupKey, int groupAlertBehavior) {
         return getNotificationRecord(mId, false, false, true, false, false, true, true, true,
-            groupKey, groupAlertBehavior, false);
+            groupKey, groupAlertBehavior, false, mUser);
     }
 
     private NotificationRecord getLightsNotificationRecord(String groupKey,
         int groupAlertBehavior) {
         return getNotificationRecord(mId, false, false, false, false, true /*lights*/, true,
-            true, true, groupKey, groupAlertBehavior, false);
+            true, true, groupKey, groupAlertBehavior, false, mUser);
     }
 
     private NotificationRecord getNotificationRecord(int id,
         boolean insistent, boolean once,
         boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
         boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
-        boolean isLeanback) {
+        boolean isLeanback, UserHandle userHandle) {
 
         final Builder builder = new Builder(getContext())
             .setContentTitle("foo")
@@ -399,7 +425,7 @@
             .thenReturn(isLeanback);
 
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
-            mPid, n, mUser, null, System.currentTimeMillis());
+            mPid, n, userHandle, null, System.currentTimeMillis());
         NotificationRecord r = new NotificationRecord(context, sbn, mChannel);
         mService.addNotification(r);
         return r;
@@ -410,19 +436,26 @@
     //
 
     private void verifyNeverBeep() throws RemoteException {
-        verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any());
+        verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any(), anyFloat());
     }
 
     private void verifyBeepUnlooped() throws RemoteException  {
-        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any());
+        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any(),
+                eq(DEFAULT_VOLUME));
     }
 
     private void verifyBeepLooped() throws RemoteException  {
-        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any());
+        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any(),
+                eq(DEFAULT_VOLUME));
     }
 
     private void verifyBeep(int times)  throws RemoteException  {
-        verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any());
+        verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any(),
+                eq(DEFAULT_VOLUME));
+    }
+
+    private void verifyBeepVolume(float volume)  throws RemoteException  {
+        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), anyBoolean(), any(), eq(volume));
     }
 
     private void verifyNeverStopAudio() throws RemoteException {
@@ -921,7 +954,7 @@
             mAttentionHelper.getVibratorHelper().createFallbackVibration(
                 /* insistent= */ false));
         verify(mRingtonePlayer, never()).playAsync(anyObject(), anyObject(), anyBoolean(),
-            anyObject());
+            anyObject(), anyFloat());
         assertTrue(r.isInterruptive());
         assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
@@ -1948,6 +1981,259 @@
         assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
+    // TODO b/270456865: Only one of the two strategies will be released.
+    //  The other one need to be removed
+    @Test
+    public void testBeepVolume_politeNotif() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        initAttentionHelper(flagResolver);
+
+        NotificationRecord r = getBeepyNotification();
+
+        // set up internal state
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        Mockito.reset(mRingtonePlayer);
+
+        // update should beep at 50% volume
+        r.isUpdate = true;
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyBeepVolume(0.5f);
+
+        // 2nd update should beep at 0% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyBeepVolume(0.0f);
+
+        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+    }
+
+    // TODO b/270456865: Only one of the two strategies will be released.
+    //  The other one need to be removed
+    @Test
+    public void testBeepVolume_politeNotif_Strategy2() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule2");
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        initAttentionHelper(flagResolver);
+
+        NotificationRecord r = getBeepyNotification();
+
+        // set up internal state
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        Mockito.reset(mRingtonePlayer);
+
+        // update should beep at 0% volume
+        r.isUpdate = true;
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyBeepVolume(0.0f);
+
+        // 2nd update should beep at 50% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyBeepVolume(0.5f);
+
+        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+    }
+
+    @Test
+    public void testVibrationIntensity_politeNotif() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        initAttentionHelper(flagResolver);
+
+        NotificationRecord r = getBuzzyBeepyNotification();
+
+        // set up internal state
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+
+        VibratorHelper vibratorHelper = mAttentionHelper.getVibratorHelper();
+        Mockito.reset(vibratorHelper);
+
+        // update should buzz at 50% intensity
+        r.isUpdate = true;
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+
+        verify(vibratorHelper, times(1)).scale(any(), eq(0.5f));
+        Mockito.reset(vibratorHelper);
+
+        // 2nd update should buzz at 0% intensity
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verify(vibratorHelper, times(1)).scale(any(), eq(0.0f));
+    }
+
+    @Test
+    public void testVibrationIntensity_politeNotif_Strategy2() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule2");
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        initAttentionHelper(flagResolver);
+
+        NotificationRecord r = getBuzzyBeepyNotification();
+
+        // set up internal state
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+
+        VibratorHelper vibratorHelper = mAttentionHelper.getVibratorHelper();
+        Mockito.reset(vibratorHelper);
+
+        // update should buzz at 0% intensity
+        r.isUpdate = true;
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+
+        verify(vibratorHelper, times(1)).scale(any(), eq(0.0f));
+        Mockito.reset(vibratorHelper);
+
+        // 2nd update should buzz at 50% intensity
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verify(vibratorHelper, times(1)).scale(any(), eq(0.5f));
+    }
+
+    @Test
+    public void testBuzzOnlyOnScreenUnlock_politeNotif() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+
+        // When NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED setting is enabled
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, 1);
+
+        initAttentionHelper(flagResolver);
+        // And screen is unlocked
+        mAttentionHelper.setUserPresent(true);
+
+        NotificationRecord r = getBuzzyBeepyNotification();
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+
+        // The notification attention should only buzz
+        verifyNeverBeep();
+        verifyVibrate();
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+    }
+
+    @Test
+    public void testBeepVolume_politeNotif_disabled() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+
+        // When NOTIFICATION_COOLDOWN_ENABLED setting is disabled
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_ENABLED, 0);
+
+        initAttentionHelper(flagResolver);
+
+        NotificationRecord r = getBeepyNotification();
+
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        Mockito.reset(mRingtonePlayer);
+
+        // update should beep at 100% volume
+        r.isUpdate = true;
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyBeepVolume(1.0f);
+
+        // 2nd update should beep at 100% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyBeepVolume(1.0f);
+
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+    }
+
+    @Test
+    public void testBeepVolume_politeNotif_workProfile() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+
+        final int workProfileUserId = mUser.getIdentifier() + 1;
+
+        // Enable notifications cooldown for work profile
+        Settings.System.putIntForUser(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_ENABLED, 1, workProfileUserId);
+
+        when(mUserManager.getProfiles(mUser.getIdentifier())).thenReturn(
+                List.of(new UserInfo(workProfileUserId, "work_profile", null,
+                        UserInfo.FLAG_PROFILE | UserInfo.FLAG_MANAGED_PROFILE,
+                        UserInfo.getDefaultUserType(UserInfo.FLAG_MANAGED_PROFILE))));
+
+        initAttentionHelper(flagResolver);
+
+        final NotificationRecord r = getBuzzyBeepyNotification(UserHandle.of(workProfileUserId));
+
+        // set up internal state
+        mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
+        Mockito.reset(mRingtonePlayer);
+
+        // update should beep at 50% volume
+        r.isUpdate = true;
+        mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
+        verifyBeepVolume(0.5f);
+
+        // 2nd update should beep at 0% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
+        verifyBeepVolume(0.0f);
+
+        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+    }
+
+    @Test
+    public void testBeepVolume_politeNotif_workProfile_disabled() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+
+        final int workProfileUserId = mUser.getIdentifier() + 1;
+
+        // Disable notifications cooldown for work profile
+        Settings.System.putIntForUser(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_ENABLED, 0, workProfileUserId);
+
+        when(mUserManager.getProfiles(mUser.getIdentifier())).thenReturn(
+                List.of(new UserInfo(workProfileUserId, "work_profile", null,
+                        UserInfo.FLAG_PROFILE | UserInfo.FLAG_MANAGED_PROFILE,
+                        UserInfo.getDefaultUserType(UserInfo.FLAG_MANAGED_PROFILE))));
+
+        initAttentionHelper(flagResolver);
+
+        final NotificationRecord r = getBuzzyBeepyNotification(UserHandle.of(workProfileUserId));
+
+        mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
+        Mockito.reset(mRingtonePlayer);
+
+        // update should beep at 100% volume
+        r.isUpdate = true;
+        mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
+        verifyBeepVolume(1.0f);
+
+        // 2nd update should beep at 100% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
+        verifyBeepVolume(1.0f);
+
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+    }
+
     static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
         private final int mRepeatIndex;
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index c05f814..53ca704 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -23,9 +23,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
@@ -49,12 +47,10 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
-import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.NotificationRankingUpdate;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
@@ -158,81 +154,6 @@
         }
     }
 
-    // Tests parceling of NotificationRankingUpdate, and by extension, RankingMap and Ranking.
-    @Test
-    public void testRankingUpdate_parcel() {
-        NotificationRankingUpdate nru = generateUpdate();
-        Parcel parcel = Parcel.obtain();
-        nru.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        NotificationRankingUpdate nru1 = NotificationRankingUpdate.CREATOR.createFromParcel(parcel);
-        assertEquals(nru, nru1);
-    }
-
-    // Tests parceling of RankingMap and RankingMap.equals
-    @Test
-    public void testRankingMap_parcel() {
-        RankingMap rmap = generateUpdate().getRankingMap();
-        Parcel parcel = Parcel.obtain();
-        rmap.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        RankingMap rmap1 = RankingMap.CREATOR.createFromParcel(parcel);
-
-        detailedAssertEquals(rmap, rmap1);
-        assertEquals(rmap, rmap1);
-    }
-
-    // Tests parceling of Ranking and Ranking.equals
-    @Test
-    public void testRanking_parcel() {
-        Ranking ranking = generateUpdate().getRankingMap().getRawRankingObject(mKeys[0]);
-        Parcel parcel = Parcel.obtain();
-        ranking.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        Ranking ranking1 = new Ranking(parcel);
-        detailedAssertEquals("rankings differ: ", ranking, ranking1);
-        assertEquals(ranking, ranking1);
-    }
-
-    // Tests NotificationRankingUpdate.equals(), and by extension, RankingMap and Ranking.
-    @Test
-    public void testRankingUpdate_equals() {
-        NotificationRankingUpdate nru = generateUpdate();
-        NotificationRankingUpdate nru2 = generateUpdate();
-        detailedAssertEquals(nru, nru2);
-        assertEquals(nru, nru2);
-        Ranking tweak = nru2.getRankingMap().getRawRankingObject(mKeys[0]);
-        tweak.populate(
-                tweak.getKey(),
-                tweak.getRank(),
-                !tweak.matchesInterruptionFilter(), // note the inversion here!
-                tweak.getLockscreenVisibilityOverride(),
-                tweak.getSuppressedVisualEffects(),
-                tweak.getImportance(),
-                tweak.getImportanceExplanation(),
-                tweak.getOverrideGroupKey(),
-                tweak.getChannel(),
-                (ArrayList) tweak.getAdditionalPeople(),
-                (ArrayList) tweak.getSnoozeCriteria(),
-                tweak.canShowBadge(),
-                tweak.getUserSentiment(),
-                tweak.isSuspended(),
-                tweak.getLastAudiblyAlertedMillis(),
-                tweak.isNoisy(),
-                (ArrayList) tweak.getSmartActions(),
-                (ArrayList) tweak.getSmartReplies(),
-                tweak.canBubble(),
-                tweak.isTextChanged(),
-                tweak.isConversation(),
-                tweak.getConversationShortcutInfo(),
-                tweak.getRankingAdjustment(),
-                tweak.isBubble(),
-                tweak.getProposedImportance(),
-                tweak.hasSensitiveContent()
-        );
-        assertNotEquals(nru, nru2);
-    }
-
     @Test
     public void testLegacyIcons_preM() {
         TestListenerService service = new TestListenerService();
@@ -275,7 +196,6 @@
         assertNull(n.largeIcon);
     }
 
-
     // Test data
 
     private String[] mKeys = new String[] { "key", "key1", "key2", "key3", "key4"};
@@ -461,48 +381,6 @@
         }
     }
 
-    private void detailedAssertEquals(NotificationRankingUpdate a, NotificationRankingUpdate b) {
-        detailedAssertEquals(a.getRankingMap(), b.getRankingMap());
-    }
-
-    private void detailedAssertEquals(String comment, Ranking a, Ranking b) {
-        assertEquals(comment, a.getKey(), b.getKey());
-        assertEquals(comment, a.getRank(), b.getRank());
-        assertEquals(comment, a.matchesInterruptionFilter(), b.matchesInterruptionFilter());
-        assertEquals(comment, a.getLockscreenVisibilityOverride(), b.getLockscreenVisibilityOverride());
-        assertEquals(comment, a.getSuppressedVisualEffects(), b.getSuppressedVisualEffects());
-        assertEquals(comment, a.getImportance(), b.getImportance());
-        assertEquals(comment, a.getImportanceExplanation(), b.getImportanceExplanation());
-        assertEquals(comment, a.getOverrideGroupKey(), b.getOverrideGroupKey());
-        assertEquals(comment, a.getChannel(), b.getChannel());
-        assertEquals(comment, a.getAdditionalPeople(), b.getAdditionalPeople());
-        assertEquals(comment, a.getSnoozeCriteria(), b.getSnoozeCriteria());
-        assertEquals(comment, a.canShowBadge(), b.canShowBadge());
-        assertEquals(comment, a.getUserSentiment(), b.getUserSentiment());
-        assertEquals(comment, a.isSuspended(), b.isSuspended());
-        assertEquals(comment, a.getLastAudiblyAlertedMillis(), b.getLastAudiblyAlertedMillis());
-        assertEquals(comment, a.isNoisy(), b.isNoisy());
-        assertEquals(comment, a.getSmartReplies(), b.getSmartReplies());
-        assertEquals(comment, a.canBubble(), b.canBubble());
-        assertEquals(comment, a.isConversation(), b.isConversation());
-        assertEquals(comment, a.getConversationShortcutInfo().getId(),
-                b.getConversationShortcutInfo().getId());
-        assertActionsEqual(a.getSmartActions(), b.getSmartActions());
-        assertEquals(a.getProposedImportance(), b.getProposedImportance());
-        assertEquals(a.hasSensitiveContent(), b.hasSensitiveContent());
-    }
-
-    private void detailedAssertEquals(RankingMap a, RankingMap b) {
-        Ranking arank = new Ranking();
-        Ranking brank = new Ranking();
-        assertArrayEquals(a.getOrderedKeys(), b.getOrderedKeys());
-        for (String key : a.getOrderedKeys()) {
-            a.getRanking(key, arank);
-            b.getRanking(key, brank);
-            detailedAssertEquals("ranking for key <" + key + ">", arank, brank);
-        }
-    }
-
     public static class TestListenerService extends NotificationListenerService {
         private final IBinder binder = new LocalBinder();
         public int targetSdk = 0;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 91129a1..3d4b4a6 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -687,7 +687,11 @@
                 mPackageIntentReceiver = broadcastReceivers.get(i);
             }
             if (filter.hasAction(Intent.ACTION_USER_SWITCHED)) {
-                mUserSwitchIntentReceiver = broadcastReceivers.get(i);
+                // There may be multiple receivers, get the NMS one
+                if (broadcastReceivers.get(i).toString().contains(
+                        NotificationManagerService.class.getName())) {
+                    mUserSwitchIntentReceiver = broadcastReceivers.get(i);
+                }
             }
         }
         assertNotNull("package intent receiver should exist", mPackageIntentReceiver);
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/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index 0a7bb00..71098aa 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -20,6 +20,7 @@
 import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ALL_APPS;
 import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ASSIST;
 import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_NOTIFICATION_PANEL;
+import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL;
 
 import android.platform.test.annotations.Presubmit;
 import android.view.KeyEvent;
@@ -284,6 +285,16 @@
                         KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_H, META_ON}};
     }
 
+    @Keep
+    private static Object[][] shortPressOnSettingsTestArguments() {
+        // testName, testKeys, shortPressOnSettingsBehavior, expectedLogEvent, expectedKey,
+        // expectedModifierState
+        return new Object[][]{
+                {"SETTINGS key -> Toggle Notification panel", new int[]{KeyEvent.KEYCODE_SETTINGS},
+                        SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL,
+                        KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_SETTINGS, 0}};
+    }
+
     @Before
     public void setUp() {
         setUpPhoneWindowManager(/*supportSettingsUpdate*/ true);
@@ -294,6 +305,7 @@
         mPhoneWindowManager.overrideEnableBugReportTrigger(true);
         mPhoneWindowManager.overrideStatusBarManagerInternal();
         mPhoneWindowManager.overrideStartActivity();
+        mPhoneWindowManager.overrideSendBroadcast();
         mPhoneWindowManager.overrideUserSetupComplete();
         mPhoneWindowManager.setupAssistForLaunch();
         mPhoneWindowManager.overrideTogglePanel();
@@ -330,4 +342,15 @@
         mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
                 expectedKey, expectedModifierState, "Failed while executing " + testName);
     }
+
+    @Test
+    @Parameters(method = "shortPressOnSettingsTestArguments")
+    public void testShortPressOnSettings(String testName, int[] testKeys,
+            int shortPressOnSettingsBehavior, KeyboardLogEvent expectedLogEvent, int expectedKey,
+            int expectedModifierState) {
+        mPhoneWindowManager.overrideShortPressOnSettingsBehavior(shortPressOnSettingsBehavior);
+        sendKeyCombination(testKeys, 0 /* duration */);
+        mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
+                expectedKey, expectedModifierState, "Failed while executing " + testName);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index ef28ffa..2244dbe 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -375,6 +375,10 @@
         mPhoneWindowManager.mDoubleTapOnHomeBehavior = behavior;
     }
 
+    void overrideShortPressOnSettingsBehavior(int behavior) {
+        mPhoneWindowManager.mShortPressOnSettingsBehavior = behavior;
+    }
+
     void overrideCanStartDreaming(boolean canDream) {
         doReturn(canDream).when(mDreamManagerInternal).canStartDreaming(anyBoolean());
     }
@@ -484,6 +488,10 @@
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
     }
 
+    void overrideSendBroadcast() {
+        doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
+    }
+
     void overrideUserSetupComplete() {
         doReturn(true).when(mPhoneWindowManager).isUserSetupComplete();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 233a207..84d42d42 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -18,16 +18,20 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
+import static com.android.server.wm.utils.LastCallVerifier.lastCall;
 
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 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.reset;
 import static org.mockito.Mockito.when;
 
 import android.graphics.Rect;
@@ -35,8 +39,9 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 
-import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.testutils.StubTransaction;
+import com.android.server.wm.utils.MockAnimationAdapter;
+import com.android.window.flags.Flags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -118,102 +123,168 @@
         }
     }
 
-    private MockSurfaceBuildingContainer mHost;
-    private Dimmer mDimmer;
-    private SurfaceControl.Transaction mTransaction;
-    private Dimmer.SurfaceAnimatorStarter mSurfaceAnimatorStarter;
+    static class MockAnimationAdapterFactory extends SmoothDimmer.AnimationAdapterFactory {
+        public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec,
+                SurfaceAnimationRunner runner) {
+            return sTestAnimation;
+        }
+    }
 
-    private static class SurfaceAnimatorStarterImpl implements Dimmer.SurfaceAnimatorStarter {
+    private static class SurfaceAnimatorStarterImpl implements LegacyDimmer.SurfaceAnimatorStarter {
         @Override
         public void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
-                AnimationAdapter anim, boolean hidden, @AnimationType int type) {
+                AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type) {
             surfaceAnimator.mStaticAnimationFinishedCallback.onAnimationFinished(type, anim);
         }
     }
 
+    private MockSurfaceBuildingContainer mHost;
+    private Dimmer mDimmer;
+    private SurfaceControl.Transaction mTransaction;
+    private TestWindowContainer mChild;
+    private static AnimationAdapter sTestAnimation;
+    private static LegacyDimmer.SurfaceAnimatorStarter sSurfaceAnimatorStarter;
+
     @Before
     public void setUp() throws Exception {
         mHost = new MockSurfaceBuildingContainer(mWm);
-        mSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl());
         mTransaction = spy(StubTransaction.class);
-        mDimmer = new Dimmer(mHost, mSurfaceAnimatorStarter);
+        mChild = new TestWindowContainer(mWm);
+        if (Flags.dimmerRefactor()) {
+            sTestAnimation = spy(new MockAnimationAdapter());
+            mDimmer = new SmoothDimmer(mHost, new MockAnimationAdapterFactory());
+        } else {
+            sSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl());
+            mDimmer = new LegacyDimmer(mHost, sSurfaceAnimatorStarter);
+        }
     }
 
     @Test
     public void testUpdateDimsAppliesCrop() {
-        TestWindowContainer child = new TestWindowContainer(mWm);
-        mHost.addChild(child, 0);
+        mHost.addChild(mChild, 0);
 
         final float alpha = 0.8f;
-        mDimmer.dimAbove(child, alpha);
+        mDimmer.dimAbove(mChild, alpha);
 
         int width = 100;
         int height = 300;
-        mDimmer.mDimState.mDimBounds.set(0, 0, width, height);
+        mDimmer.getDimBounds().set(0, 0, width, height);
         mDimmer.updateDims(mTransaction);
 
-        verify(mTransaction).setWindowCrop(getDimLayer(), width, height);
-        verify(mTransaction).show(getDimLayer());
+        verify(mTransaction).setWindowCrop(mDimmer.getDimLayer(), width, height);
+        verify(mTransaction).show(mDimmer.getDimLayer());
     }
 
     @Test
-    public void testDimAboveWithChildCreatesSurfaceAboveChild() {
-        TestWindowContainer child = new TestWindowContainer(mWm);
-        mHost.addChild(child, 0);
-
+    public void testDimAboveWithChildCreatesSurfaceAboveChild_Smooth() {
+        assumeTrue(Flags.dimmerRefactor());
         final float alpha = 0.8f;
-        mDimmer.dimAbove(child, alpha);
-        SurfaceControl dimLayer = getDimLayer();
+        mHost.addChild(mChild, 0);
+        mDimmer.dimAbove(mChild, alpha);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
+
+        assertNotNull("Dimmer should have created a surface", dimLayer);
+
+        mDimmer.updateDims(mTransaction);
+        verify(sTestAnimation).startAnimation(eq(dimLayer), eq(mTransaction),
+                anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+        verify(mTransaction).setRelativeLayer(dimLayer, mChild.mControl, 1);
+        verify(mTransaction, lastCall()).setAlpha(dimLayer, alpha);
+    }
+
+    @Test
+    public void testDimAboveWithChildCreatesSurfaceAboveChild_Legacy() {
+        assumeFalse(Flags.dimmerRefactor());
+        final float alpha = 0.8f;
+        mHost.addChild(mChild, 0);
+        mDimmer.dimAbove(mChild, alpha);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
 
         assertNotNull("Dimmer should have created a surface", dimLayer);
 
         verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha);
-        verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, 1);
+        verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, 1);
     }
 
     @Test
-    public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild() {
-        TestWindowContainer child = new TestWindowContainer(mWm);
-        mHost.addChild(child, 0);
+    public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Smooth() {
+        assumeTrue(Flags.dimmerRefactor());
+        final float alpha = 0.7f;
+        mHost.addChild(mChild, 0);
+        mDimmer.dimBelow(mChild, alpha, 50);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
 
-        final float alpha = 0.8f;
-        mDimmer.dimBelow(child, alpha, 0);
-        SurfaceControl dimLayer = getDimLayer();
+        assertNotNull("Dimmer should have created a surface", dimLayer);
+
+        mDimmer.updateDims(mTransaction);
+        verify(sTestAnimation).startAnimation(eq(dimLayer), eq(mTransaction),
+                anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+        verify(mTransaction).setRelativeLayer(dimLayer, mChild.mControl, -1);
+        verify(mTransaction, lastCall()).setAlpha(dimLayer, alpha);
+        verify(mTransaction).setBackgroundBlurRadius(dimLayer, 50);
+    }
+
+    @Test
+    public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Legacy() {
+        assumeFalse(Flags.dimmerRefactor());
+        final float alpha = 0.7f;
+        mHost.addChild(mChild, 0);
+        mDimmer.dimBelow(mChild, alpha, 50);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
 
         assertNotNull("Dimmer should have created a surface", dimLayer);
 
         verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha);
-        verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1);
+        verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1);
     }
 
     @Test
-    public void testDimBelowWithChildSurfaceDestroyedWhenReset() {
-        TestWindowContainer child = new TestWindowContainer(mWm);
-        mHost.addChild(child, 0);
+    public void testDimBelowWithChildSurfaceDestroyedWhenReset_Smooth() {
+        assumeTrue(Flags.dimmerRefactor());
+        mHost.addChild(mChild, 0);
 
         final float alpha = 0.8f;
-        mDimmer.dimAbove(child, alpha);
-        SurfaceControl dimLayer = getDimLayer();
+        // Dim once
+        mDimmer.dimBelow(mChild, alpha, 0);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
+        mDimmer.updateDims(mTransaction);
+        // Reset, and don't dim
+        mDimmer.resetDimStates();
+        mDimmer.updateDims(mTransaction);
+        verify(mTransaction).show(dimLayer);
+        verify(mTransaction).remove(dimLayer);
+    }
+
+    @Test
+    public void testDimBelowWithChildSurfaceDestroyedWhenReset_Legacy() {
+        assumeFalse(Flags.dimmerRefactor());
+        mHost.addChild(mChild, 0);
+
+        final float alpha = 0.8f;
+        mDimmer.dimAbove(mChild, alpha);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
         mDimmer.resetDimStates();
 
         mDimmer.updateDims(mTransaction);
-        verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any(
-                SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
+        verify(sSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class),
+                any(SurfaceControl.Transaction.class), any(AnimationAdapter.class),
+                anyBoolean(),
                 eq(ANIMATION_TYPE_DIMMER));
         verify(mHost.getPendingTransaction()).remove(dimLayer);
     }
 
     @Test
     public void testDimBelowWithChildSurfaceNotDestroyedWhenPersisted() {
-        TestWindowContainer child = new TestWindowContainer(mWm);
-        mHost.addChild(child, 0);
+        mHost.addChild(mChild, 0);
 
         final float alpha = 0.8f;
-        mDimmer.dimAbove(child, alpha);
-        SurfaceControl dimLayer = getDimLayer();
+        // Dim once
+        mDimmer.dimBelow(mChild, alpha, 0);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
+        mDimmer.updateDims(mTransaction);
+        // Reset and dim again
         mDimmer.resetDimStates();
-        mDimmer.dimAbove(child, alpha);
-
+        mDimmer.dimAbove(mChild, alpha);
         mDimmer.updateDims(mTransaction);
         verify(mTransaction).show(dimLayer);
         verify(mTransaction, never()).remove(dimLayer);
@@ -221,14 +292,12 @@
 
     @Test
     public void testDimUpdateWhileDimming() {
-        TestWindowContainer child = new TestWindowContainer(mWm);
-        mHost.addChild(child, 0);
-
+        mHost.addChild(mChild, 0);
         final float alpha = 0.8f;
-        mDimmer.dimAbove(child, alpha);
-        final Rect bounds = mDimmer.mDimState.mDimBounds;
+        mDimmer.dimAbove(mChild, alpha);
+        final Rect bounds = mDimmer.getDimBounds();
 
-        SurfaceControl dimLayer = getDimLayer();
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
         bounds.set(0, 0, 10, 10);
         mDimmer.updateDims(mTransaction);
         verify(mTransaction).setWindowCrop(dimLayer, bounds.width(), bounds.height());
@@ -242,41 +311,56 @@
     }
 
     @Test
-    public void testRemoveDimImmediately() {
-        TestWindowContainer child = new TestWindowContainer(mWm);
-        mHost.addChild(child, 0);
-
-        mDimmer.dimAbove(child, 1);
-        SurfaceControl dimLayer = getDimLayer();
+    public void testRemoveDimImmediately_Smooth() {
+        assumeTrue(Flags.dimmerRefactor());
+        mHost.addChild(mChild, 0);
+        mDimmer.dimAbove(mChild, 1);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
         mDimmer.updateDims(mTransaction);
         verify(mTransaction, times(1)).show(dimLayer);
 
-        reset(mSurfaceAnimatorStarter);
+        reset(sTestAnimation);
         mDimmer.dontAnimateExit();
         mDimmer.resetDimStates();
         mDimmer.updateDims(mTransaction);
-        verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any(
-                SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
+        verify(sTestAnimation, never()).startAnimation(
+                any(SurfaceControl.class), any(SurfaceControl.Transaction.class),
+                anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+        verify(mTransaction).remove(dimLayer);
+    }
+
+    @Test
+    public void testRemoveDimImmediately_Legacy() {
+        assumeFalse(Flags.dimmerRefactor());
+        mHost.addChild(mChild, 0);
+        mDimmer.dimAbove(mChild, 1);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
+        mDimmer.updateDims(mTransaction);
+        verify(mTransaction, times(1)).show(dimLayer);
+
+        reset(sSurfaceAnimatorStarter);
+        mDimmer.dontAnimateExit();
+        mDimmer.resetDimStates();
+        mDimmer.updateDims(mTransaction);
+        verify(sSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class),
+                any(SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
                 eq(ANIMATION_TYPE_DIMMER));
         verify(mTransaction).remove(dimLayer);
     }
 
     @Test
-    public void testDimmerWithBlurUpdatesTransaction() {
+    public void testDimmerWithBlurUpdatesTransaction_Legacy() {
+        assumeFalse(Flags.dimmerRefactor());
         TestWindowContainer child = new TestWindowContainer(mWm);
         mHost.addChild(child, 0);
 
         final int blurRadius = 50;
         mDimmer.dimBelow(child, 0, blurRadius);
-        SurfaceControl dimLayer = getDimLayer();
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
 
         assertNotNull("Dimmer should have created a surface", dimLayer);
 
         verify(mHost.getPendingTransaction()).setBackgroundBlurRadius(dimLayer, blurRadius);
         verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1);
     }
-
-    private SurfaceControl getDimLayer() {
-        return mDimmer.mDimState.mDimLayer;
-    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index cd0389d..ba31944 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -46,10 +46,13 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -583,6 +586,7 @@
         final IDisplayAreaOrganizer mockDisplayAreaOrganizer = mock(IDisplayAreaOrganizer.class);
         doReturn(mock(IBinder.class)).when(mockDisplayAreaOrganizer).asBinder();
         displayArea.mOrganizer = mockDisplayAreaOrganizer;
+        displayArea.mDisplayAreaAppearedSent = true;
         spyOn(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController);
         mDisplayContent.addChild(displayArea, 0);
 
@@ -687,6 +691,56 @@
         assertEquals(parent.getChildAt(0), child);
     }
 
+    @Test
+    public void testSetOrganizer() {
+        final TaskDisplayArea displayArea = createTaskDisplayArea(
+                mDisplayContent, mWm, "NewArea", FEATURE_VENDOR_FIRST);
+
+        assertNull(displayArea.mOrganizer);
+        assertFalse(displayArea.mDisplayAreaAppearedSent);
+
+        final IDisplayAreaOrganizer organizer = mock(IDisplayAreaOrganizer.class);
+        final DisplayAreaOrganizerController controller =
+                mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController;
+        spyOn(controller);
+        doNothing().when(controller).onDisplayAreaVanished(any(), any());
+
+        displayArea.setOrganizer(organizer);
+
+        assertEquals(organizer, displayArea.mOrganizer);
+        assertTrue(displayArea.mDisplayAreaAppearedSent);
+        verify(controller).onDisplayAreaAppeared(organizer, displayArea);
+
+        // No duplicated appeared sent.
+        clearInvocations(controller);
+        displayArea.sendDisplayAreaAppeared();
+
+        verify(controller, never()).onDisplayAreaAppeared(any(), any());
+
+        // Sent info changed after appeared.
+        displayArea.sendDisplayAreaInfoChanged();
+
+        verify(controller).onDisplayAreaInfoChanged(organizer, displayArea);
+
+        // Sent info vanished after appeared.
+        displayArea.setOrganizer(null);
+
+        verify(controller).onDisplayAreaVanished(organizer, displayArea);
+        assertNull(displayArea.mOrganizer);
+        assertFalse(displayArea.mDisplayAreaAppearedSent);
+
+        // No callback until appeared sent.
+        clearInvocations(controller);
+
+        displayArea.sendDisplayAreaAppeared();
+        displayArea.sendDisplayAreaInfoChanged();
+        displayArea.sendDisplayAreaVanished(organizer);
+
+        verify(controller, never()).onDisplayAreaAppeared(any(), any());
+        verify(controller, never()).onDisplayAreaInfoChanged(any(), any());
+        verify(controller, never()).onDisplayAreaVanished(any(), any());
+    }
+
     private static class TestDisplayArea<T extends WindowContainer> extends DisplayArea<T> {
         private TestDisplayArea(WindowManagerService wms, Rect bounds, String name) {
             super(wms, ANY, name);
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/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 5205bb0..7822071 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -37,6 +37,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_PARENT_TASK;
+import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_TASK_FRAGMENT;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -687,4 +689,24 @@
         tf0.setIsolatedNav(true);
         assertTrue(tf0.isIsolatedNav());
     }
+
+    @Test
+    public void testGetDimBounds() {
+        final Task task = mTaskFragment.getTask();
+        final Rect taskBounds = task.getBounds();
+        mTaskFragment.setBounds(taskBounds.left, taskBounds.top, taskBounds.left + 10,
+                taskBounds.top + 10);
+        final Rect taskFragmentBounds = mTaskFragment.getBounds();
+
+        // Return Task bounds if dimming on parent Task.
+        final Rect dimBounds = new Rect();
+        mTaskFragment.setEmbeddedDimArea(EMBEDDED_DIM_AREA_PARENT_TASK);
+        mTaskFragment.getDimBounds(dimBounds);
+        assertEquals(taskBounds, dimBounds);
+
+        // Return TF bounds by default.
+        mTaskFragment.setEmbeddedDimArea(EMBEDDED_DIM_AREA_TASK_FRAGMENT);
+        mTaskFragment.getDimBounds(dimBounds);
+        assertEquals(taskFragmentBounds, dimBounds);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 474720f..c8546c6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -48,6 +48,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.window.flags.Flags.explicitRefreshRateHints;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -60,6 +61,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
@@ -84,6 +86,7 @@
 import android.window.ITaskOrganizer;
 import android.window.ITransitionPlayer;
 import android.window.RemoteTransition;
+import android.window.SystemPerformanceHinter;
 import android.window.TaskFragmentOrganizer;
 import android.window.TransitionInfo;
 
@@ -2433,6 +2436,45 @@
         assertTrue((player.mLastReady.getFlags() & FLAG_SYNC) == 0);
     }
 
+    @Test
+    public void testTransitionsTriggerPerformanceHints() {
+        assumeTrue(explicitRefreshRateHints());
+        SystemPerformanceHinter systemPerformanceHinter = mock(SystemPerformanceHinter.class);
+        final TransitionController controller = new TestTransitionController(mAtm);
+        final TestTransitionPlayer player = registerTestTransitionPlayer();
+
+        mSyncEngine = createTestBLASTSyncEngine();
+        controller.setSyncEngine(mSyncEngine);
+        controller.setSystemPerformanceHinter(systemPerformanceHinter);
+        SystemPerformanceHinter.HighPerfSession session = mock(
+                SystemPerformanceHinter.HighPerfSession.class);
+        doReturn(session).when(systemPerformanceHinter).startSession(anyInt(), anyInt(),
+                anyString());
+
+        final Transition transitA = createTestTransition(TRANSIT_OPEN, controller);
+        final Task task = createTask(mDisplayContent,
+                WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
+        final ActivityRecord act = createActivityRecord(task);
+        act.setVisibleRequested(true);
+        act.setVisible(true);
+
+        controller.startCollectOrQueue(transitA, (deferred) -> {
+        });
+        transitA.collect(act);
+
+        verify(systemPerformanceHinter).startSession(
+                eq(SystemPerformanceHinter.HINT_SF), anyInt(), eq("Transition collected"));
+
+        transitA.start();
+        transitA.setAllReady();
+
+        // Aborting here doesn't abort the transition, it aborts the sync allowing the transition to
+        // finish successfully.
+        mSyncEngine.abort(transitA.getSyncId());
+        controller.finishTransition(transitA);
+        verify(session).close();
+    }
+
     private static void makeTaskOrganized(Task... tasks) {
         final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
         for (Task t : tasks) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java b/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java
new file mode 100644
index 0000000..320d094
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java
@@ -0,0 +1,68 @@
+/*
+ * 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.wm.utils;
+
+import org.mockito.internal.verification.VerificationModeFactory;
+import org.mockito.internal.verification.api.VerificationData;
+import org.mockito.invocation.Invocation;
+import org.mockito.invocation.MatchableInvocation;
+import org.mockito.verification.VerificationMode;
+
+import java.util.List;
+
+/**
+ * Verifier to check that the last call of a method received the expected argument
+ */
+public class LastCallVerifier implements VerificationMode {
+
+    /**
+     * Allows comparing the expected invocation with the last invocation on the same method
+     */
+    public static LastCallVerifier lastCall() {
+        return new LastCallVerifier();
+    }
+
+    @Override
+    public void verify(VerificationData data) {
+        List<Invocation> invocations = data.getAllInvocations();
+        MatchableInvocation target = data.getTarget();
+        for (int i = invocations.size() - 1; i >= 0; i--) {
+            final Invocation invocation = invocations.get(i);
+            if (target.hasSameMethod(invocation)) {
+                if (target.matches(invocation)) {
+                    return;
+                } else {
+                    throw new LastCallMismatch(target.getInvocation(), invocation, invocations);
+                }
+            }
+        }
+        throw new RuntimeException(target + " never invoked");
+    }
+
+    @Override
+    public VerificationMode description(String description) {
+        return VerificationModeFactory.description(this, description);
+    }
+
+    static class LastCallMismatch extends RuntimeException {
+        LastCallMismatch(
+                Invocation expected, Invocation received, List<Invocation> allInvocations) {
+            super("Expected invocation " + expected + " but received " + received
+                    + " as the last invocation.\nAll registered invocations:\n" + allInvocations);
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java b/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java
new file mode 100644
index 0000000..1a66970a
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.utils;
+
+import android.util.proto.ProtoOutputStream;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.wm.AnimationAdapter;
+import com.android.server.wm.SurfaceAnimator;
+
+import java.io.PrintWriter;
+
+/**
+ * An empty animation adapter which just executes the finish callback
+ */
+public class MockAnimationAdapter implements AnimationAdapter {
+
+    @Override
+    public boolean getShowWallpaper() {
+        return false;
+    }
+
+    @Override
+    public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
+            int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+        // As the animation won't run, finish it immediately
+        finishCallback.onAnimationFinished(0, null);
+    }
+
+    @Override
+    public void onAnimationCancelled(SurfaceControl animationLeash) {}
+
+    @Override
+    public long getDurationHint() {
+        return 0;
+    }
+
+    @Override
+    public long getStatusBarTransitionsStartTime() {
+        return 0;
+    }
+
+    @Override
+    public void dump(PrintWriter pw, String prefix) {}
+
+    @Override
+    public void dumpDebug(ProtoOutputStream proto) {}
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index f3bf026..2e6278d 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -53,6 +53,7 @@
 import android.app.usage.BroadcastResponseStatsList;
 import android.app.usage.ConfigurationStats;
 import android.app.usage.EventStats;
+import android.app.usage.Flags;
 import android.app.usage.IUsageStatsManager;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageEvents.Event;
@@ -200,6 +201,7 @@
     static final int MSG_ON_START = 7;
     static final int MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK = 8;
     static final int MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED = 9;
+    static final int MSG_UID_REMOVED = 10;
 
     private final Object mLock = new Object();
     private Handler mHandler;
@@ -378,11 +380,10 @@
 
         IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_STARTED);
-        getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
-                null, mHandler);
-
+        getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL,
+                filter, null, /* Handler scheduler */ null);
         getContext().registerReceiverAsUser(new UidRemovedReceiver(), UserHandle.ALL,
-                new IntentFilter(ACTION_UID_REMOVED), null, mHandler);
+                new IntentFilter(ACTION_UID_REMOVED), null, /* Handler scheduler */ null);
 
         mRealTimeSnapshot = SystemClock.elapsedRealtime();
         mSystemTimeSnapshot = System.currentTimeMillis();
@@ -614,7 +615,6 @@
             if (Intent.ACTION_USER_REMOVED.equals(action)) {
                 if (userId >= 0) {
                     mHandler.obtainMessage(MSG_REMOVE_USER, userId, 0).sendToTarget();
-                    mResponseStatsTracker.onUserRemoved(userId);
                 }
             } else if (Intent.ACTION_USER_STARTED.equals(action)) {
                 if (userId >= 0) {
@@ -632,9 +632,7 @@
                 return;
             }
 
-            synchronized (mLock) {
-                mResponseStatsTracker.onUidRemoved(uid);
-            }
+            mHandler.obtainMessage(MSG_UID_REMOVED, uid, 0).sendToTarget();
         }
     }
 
@@ -1301,6 +1299,8 @@
             mPendingLaunchTimeChangePackages.remove(userId);
         }
         mAppStandby.onUserRemoved(userId);
+        mResponseStatsTracker.onUserRemoved(userId);
+
         // Cancel any scheduled jobs for this user since the user is being removed.
         UsageStatsIdleService.cancelPruneJob(getContext(), userId);
         UsageStatsIdleService.cancelUpdateMappingsJob(getContext(), userId);
@@ -2037,6 +2037,9 @@
                 case MSG_REMOVE_USER:
                     onUserRemoved(msg.arg1);
                     break;
+                case MSG_UID_REMOVED:
+                    mResponseStatsTracker.onUidRemoved(msg.arg1);
+                    break;
                 case MSG_PACKAGE_REMOVED:
                     onPackageRemoved(msg.arg1, (String) msg.obj);
                     break;
@@ -2124,12 +2127,15 @@
         }
 
         private boolean canReportUsageStats() {
-            if (isCallingUidSystem()) {
-                return true; // System UID can always report UsageStats
+            final boolean isSystem = isCallingUidSystem();
+            if (!Flags.reportUsageStatsPermission()) {
+                // If the flag is disabled, do no check for the new permission and instead return
+                // true only if the calling uid is system since System UID can always report stats.
+                return isSystem;
             }
-
-            return getContext().checkCallingPermission(Manifest.permission.REPORT_USAGE_STATS)
-                    == PackageManager.PERMISSION_GRANTED;
+            return isSystem
+                    || getContext().checkCallingPermission(Manifest.permission.REPORT_USAGE_STATS)
+                        == PackageManager.PERMISSION_GRANTED;
         }
 
         private boolean hasObserverPermission() {
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/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 7d9b379..def52a5 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -942,6 +942,7 @@
          * @return the Telecom identifier associated with this {@link Call} . This is not a stable
          * identifier and is not guaranteed to be unique across device reboots.
          */
+        @FlaggedApi(Flags.FLAG_CALL_DETAILS_ID_CHANGES)
         public @NonNull String getId() { return mTelecomCallId; }
 
         /** {@hide} */
@@ -1894,7 +1895,7 @@
      * Tones are both played locally for the user to hear and sent to the network to be relayed
      * to the remote device.
      * <p>
-     * You must ensure that any call to {@link #playDtmfTone(char}) is followed by a matching
+     * You must ensure that any call to {@link #playDtmfTone(char)} is followed by a matching
      * call to {@link #stopDtmfTone()} and that each tone is stopped before a new one is started.
      * The play and stop commands are relayed to the underlying
      * {@link android.telecom.ConnectionService} as executed; implementations may not correctly
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 50f2ad4..24d3918 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -225,7 +225,7 @@
 
     /**
      * Request start a call streaming session. On receiving valid request, telecom will bind to
-     * the {@link CallStreamingService} implemented by a general call streaming sender. So that the
+     * the {@code CallStreamingService} implemented by a general call streaming sender. So that the
      * call streaming sender can perform streaming local device audio to another remote device and
      * control the call during streaming.
      *
diff --git a/telecomm/java/android/telecom/CallControlCallback.java b/telecomm/java/android/telecom/CallControlCallback.java
index eac2e64..0166022 100644
--- a/telecomm/java/android/telecom/CallControlCallback.java
+++ b/telecomm/java/android/telecom/CallControlCallback.java
@@ -69,7 +69,7 @@
     /**
      * Telecom is informing the client to answer an incoming call and set it to active.
      *
-     * @param videoState   see {@link android.telecom.CallAttributes.CallType} for valid states
+     * @param videoState   the video state
      * @param wasCompleted The {@link Consumer} to be completed. If the client can answer the call
      *                     on their end, {@link Consumer#accept(Object)} should be called with
      *                     {@link Boolean#TRUE}.
diff --git a/telecomm/java/android/telecom/PhoneAccountSuggestion.java b/telecomm/java/android/telecom/PhoneAccountSuggestion.java
index d9f89d5..c83804f 100644
--- a/telecomm/java/android/telecom/PhoneAccountSuggestion.java
+++ b/telecomm/java/android/telecom/PhoneAccountSuggestion.java
@@ -68,7 +68,7 @@
 
     /**
      * Creates a new instance of {@link PhoneAccountSuggestion}. This constructor is intended for
-     * use by apps implementing a {@link PhoneAccountSuggestionService}, and generally should not be
+     * use by apps implementing a {@code PhoneAccountSuggestionService}, and generally should not be
      * used by dialer apps other than for testing purposes.
      *
      * @param handle The {@link PhoneAccountHandle} for this suggestion.
diff --git a/telecomm/java/android/telecom/StreamingCall.java b/telecomm/java/android/telecom/StreamingCall.java
index 29f436d..ad1b6f9 100644
--- a/telecomm/java/android/telecom/StreamingCall.java
+++ b/telecomm/java/android/telecom/StreamingCall.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
@@ -25,6 +26,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.server.telecom.flags.Flags;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -57,6 +60,7 @@
     /**
      * The ID associated with this call.  This is the same value as {@link CallControl#getCallId()}.
      */
+    @FlaggedApi(Flags.FLAG_CALL_DETAILS_ID_CHANGES)
     public static final String EXTRA_CALL_ID = "android.telecom.extra.CALL_ID";
 
     /**
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 98bbb40..7a0bf90 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5073,7 +5073,6 @@
          * MMTEL and RCS.
          * <p>
          * The default value for this configuration is {@code false}.
-         * @see android.telephony.ims.SipDelegateManager
          */
         public static final String KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL =
                 KEY_PREFIX + "ims_single_registration_required_bool";
@@ -5648,8 +5647,8 @@
          *     <li>{@link #KEY_CAPABILITY_TYPE_SMS_INT_ARRAY}</li>
          *     <li>{@link #KEY_CAPABILITY_TYPE_CALL_COMPOSER_INT_ARRAY}</li>
          * </ul>
-         * <p> The values are defined in
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech}
+         * <p> The values are defined as {@code REGISTRATION_TECH_*} constants in
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase}.
          *
          * changing mmtel_requires_provisioning_bundle requires changes to
          * carrier_volte_provisioning_required_bool and vice versa
@@ -5661,12 +5660,12 @@
         /**
          * List of different RAT technologies on which Provisioning for Voice calling (IR.92)
          * is supported.
-         * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE
          * <p>Possible values are,
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR}
+         * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE
          */
         public static final String KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY =
                 KEY_PREFIX + "capability_type_voice_int_array";
@@ -5674,12 +5673,12 @@
         /**
          * List of different RAT technologies on which Provisioning for Video Telephony (IR.94)
          * is supported.
-         * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO
          * <p>Possible values are,
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR}
+         * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO
          */
         public static final String KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY =
                 KEY_PREFIX + "capability_type_video_int_array";
@@ -5687,24 +5686,24 @@
         /**
          * List of different RAT technologies on which Provisioning for XCAP over Ut for
          * supplementary services. (IR.92) is supported.
-         * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT
          * <p>Possible values are,
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR}
+         * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT
          */
         public static final String KEY_CAPABILITY_TYPE_UT_INT_ARRAY =
                 KEY_PREFIX + "capability_type_ut_int_array";
 
         /**
          * List of different RAT technologies on which Provisioning for SMS (IR.92) is supported.
-         * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS
          * <p>Possible values are,
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR}
+         * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS
          */
         public static final String KEY_CAPABILITY_TYPE_SMS_INT_ARRAY =
                 KEY_PREFIX + "capability_type_sms_int_array";
@@ -5712,12 +5711,12 @@
         /**
          * List of different RAT technologies on which Provisioning for Call Composer
          * (section 2.4 of RCC.20) is supported.
-         * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_CALL_COMPOSER
          * <p>Possible values are,
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR}
+         * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_CALL_COMPOSER
          */
         public static final String KEY_CAPABILITY_TYPE_CALL_COMPOSER_INT_ARRAY =
                 KEY_PREFIX + "capability_type_call_composer_int_array";
@@ -5732,8 +5731,8 @@
          *     <li>{@link #KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY}</li>
          *     <li>{@link #KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY}</li>
          * </ul>
-         * <p> The values are defined in
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech}
+         * <p> The values are defined as {@code REGISTRATION_TECH_*} constants in
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase}.
          */
         public static final String KEY_RCS_REQUIRES_PROVISIONING_BUNDLE =
                 KEY_PREFIX + "rcs_requires_provisioning_bundle";
@@ -5742,12 +5741,11 @@
          * This carrier supports User Capability Exchange using SIP OPTIONS as defined by the
          * framework. If set, the RcsFeature should support capability exchange using SIP OPTIONS.
          * If not set, this RcsFeature should not service capability requests.
-         * @see RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE
          * <p>Possible values are,
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR}
          */
         public static final String KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY =
                 KEY_PREFIX + "capability_type_options_uce_int_array";
@@ -5757,12 +5755,11 @@
          * framework. If set, the RcsFeature should support capability exchange using a presence
          * server. If not set, this RcsFeature should not publish capabilities or service capability
          * requests using presence.
-         * @see RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE
          * <p>Possible values are,
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
-         * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM}
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR}
          */
         public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY =
                 KEY_PREFIX + "capability_type_presence_uce_int_array";
@@ -9640,7 +9637,7 @@
     /**
      * A list of premium capabilities the carrier supports. Applications can prompt users to
      * purchase these premium capabilities from their carrier for a performance boost.
-     * Valid values are any of {@link TelephonyManager.PremiumCapability}.
+     * Valid values are any of {@link TelephonyManager}'s {@code PREMIUM_CAPABILITY_*} constants.
      *
      * This is empty by default, indicating that no premium capabilities are supported.
      *
@@ -10526,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/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index f9844bc..a8c077d 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -16,9 +16,12 @@
 
 package android.telephony;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 
+import com.android.internal.telephony.flags.Flags;
+
 /**
  * Describes the cause of a disconnected call. Those disconnect causes can be converted into a more
  * generic {@link android.telecom.DisconnectCause} object.
@@ -363,6 +366,7 @@
     /**
      * Indicates that the call was unable to be made because the satellite modem is enabled.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_ENABLED = 82;
 
     //*********************************************************************************************
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 631013f..7ccc27e 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -30,6 +31,8 @@
 import android.telephony.Annotation.NetworkType;
 import android.text.TextUtils;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -206,6 +209,7 @@
     /**
      * MMS service
      */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
     public static final int SERVICE_TYPE_MMS = 6;
 
     /** @hide  */
@@ -625,7 +629,7 @@
     }
 
     /**
-     * @return The access network technology {@link NetworkType}.
+     * @return The access network technology network type..
      */
     public @NetworkType int getAccessNetworkTechnology() {
         return mAccessNetworkTechnology;
@@ -702,6 +706,7 @@
      *
      * @return {@code true} if network is a non-terrestrial network else {@code false}.
      */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
     public boolean isNonTerrestrialNetwork() {
         return mIsNonTerrestrialNetwork;
     }
@@ -1186,6 +1191,7 @@
          *                                            else {@code false}.
          * @return The builder.
          */
+        @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
         public @NonNull Builder setIsNonTerrestrialNetwork(boolean isNonTerrestrialNetwork) {
             mIsNonTerrestrialNetwork = isNonTerrestrialNetwork;
             return this;
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 5b8848c..85a85c6 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -35,6 +36,7 @@
 import android.telephony.NetworkRegistrationInfo.NRState;
 import android.text.TextUtils;
 
+import com.android.internal.telephony.flags.Flags;
 import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
@@ -2256,6 +2258,7 @@
      *
      * @return {@code true} if device is connected to a non-terrestrial network else {@code false}.
      */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
     public boolean isUsingNonTerrestrialNetwork() {
         synchronized (mNetworkRegistrationInfos) {
             for (NetworkRegistrationInfo nri : mNetworkRegistrationInfos) {
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index fa5fd87..8e90fe7 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1364,7 +1364,7 @@
     public static class OnSubscriptionsChangedListener {
 
         /**
-         * After {@link Build.VERSION_CODES.Q}, it is no longer necessary to instantiate a
+         * After {@link Build.VERSION_CODES#Q}, it is no longer necessary to instantiate a
          * Handler inside of the OnSubscriptionsChangedListener in all cases, so it will only
          * be done for callers that do not supply an Executor.
          */
@@ -1388,13 +1388,14 @@
         /**
          * Create an OnSubscriptionsChangedListener.
          *
-         * For callers targeting {@link Build.VERSION_CODES.P} or earlier, this can only be called
+         * For callers targeting {@link Build.VERSION_CODES#P} or earlier, this can only be called
          * on a thread that already has a prepared Looper. Callers targeting Q or later should
          * subsequently use {@link SubscriptionManager#addOnSubscriptionsChangedListener(
          * Executor, OnSubscriptionsChangedListener)}.
          *
-         * On OS versions prior to {@link Build.VERSION_CODES.V} callers should assume that this
-         * call will fail if invoked on a thread that does not already have a prepared looper.
+         * On OS versions prior to {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} callers should
+         * assume that this call will fail if invoked on a thread that does not already have a
+         * prepared looper.
          */
         public OnSubscriptionsChangedListener() {
             mCreatorLooper = Looper.myLooper();
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 234ca91..90fa69f 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -25,6 +25,7 @@
 import android.Manifest;
 import android.annotation.BytesLong;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.LongDef;
 import android.annotation.NonNull;
@@ -128,6 +129,7 @@
 import com.android.internal.telephony.OperatorInfo;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.flags.Flags;
 import com.android.telephony.Rlog;
 
 import java.io.IOException;
@@ -1195,6 +1197,7 @@
      * The dialer app receives this event via
      * {@link Call.Callback#onConnectionEvent(Call, String, Bundle)}.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final String EVENT_DISPLAY_SOS_MESSAGE =
             "android.telephony.event.DISPLAY_SOS_MESSAGE";
 
@@ -13132,8 +13135,8 @@
      * </ul>
      *
      * @param executor The executor on which the result listener will be called.
-     * @param resultListener {@link Consumer} that will be called with the result fetched
-     *                       from the radio of type {@link CarrierRestrictionStatus}
+     * @param resultListener {@link Consumer} that will be called with the carrier restriction
+     *                       status result fetched from the radio
      * @throws SecurityException if the caller does not have the required permission/privileges or
      *                           if the caller is not pre-registered.
      */
@@ -13465,7 +13468,7 @@
     public static final int DATA_ENABLED_REASON_THERMAL = 3;
 
     /**
-     * To indicate data was enabled or disabled due to {@link MobileDataPolicy} overrides.
+     * To indicate data was enabled or disabled due to mobile data policy overrides.
      * Note that this is not a valid reason for {@link #setDataEnabledForReason(int, boolean)} and
      * is only used to indicate that data enabled was changed due to an override.
      */
@@ -14571,7 +14574,7 @@
      * @param needValidation whether validation is needed before switch happens.
      * @param executor The executor of where the callback will execute.
      * @param callback Callback will be triggered once it succeeds or failed.
-     *                 See {@link TelephonyManager.SetOpportunisticSubscriptionResult}
+     *                 See the {@code SET_OPPORTUNISTIC_SUB_*} constants
      *                 for more details. Pass null if don't care about the result.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
@@ -15036,6 +15039,7 @@
      * @hide
      */
     @TestApi
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int HAL_SERVICE_SATELLITE = 8;
 
     /** @hide */
@@ -17516,7 +17520,7 @@
     public @interface PurchasePremiumCapabilityResult {}
 
     /**
-     * Returns the purchase result {@link PurchasePremiumCapabilityResult} as a String.
+     * Returns the purchase result as a String.
      *
      * @param result The purchase premium capability result.
      * @return The purchase result as a String.
@@ -17570,7 +17574,6 @@
      * @param capability The premium capability to purchase.
      * @param executor The callback executor for the response.
      * @param callback The result of the purchase request.
-     *                 One of {@link PurchasePremiumCapabilityResult}.
      * @throws SecurityException if the caller does not hold permissions
      *         READ_BASIC_PHONE_STATE or INTERNET.
      * @see #isPremiumCapabilityAvailableForPurchase(int) to check whether the capability is valid.
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 26c17a4..e9af486 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -532,7 +532,7 @@
     /**
      * Returns the default MTU (Maximum Transmission Unit) size in bytes of the IPv4 routes brought
      * up by this APN setting. Note this value will only be used when MTU size is not provided
-     * in {@link DataCallResponse#getMtuV4()} during network bring up.
+     * in {@code DataCallResponse#getMtuV4()} during network bring up.
      *
      * @return the MTU size in bytes of the route.
      */
@@ -542,7 +542,7 @@
 
     /**
      * Returns the MTU size of the IPv6 mobile interface to which the APN connected. Note this value
-     * will only be used when MTU size is not provided in {@link DataCallResponse#getMtuV6()}
+     * will only be used when MTU size is not provided in {@code DataCallResponse#getMtuV6()}
      * during network bring up.
      *
      * @return the MTU size in bytes of the route.
@@ -1787,7 +1787,7 @@
         /**
          * Set the default MTU (Maximum Transmission Unit) size in bytes of the IPv4 routes brought
          * up by this APN setting. Note this value will only be used when MTU size is not provided
-         * in {@link DataCallResponse#getMtuV4()} during network bring up.
+         * in {@code DataCallResponse#getMtuV4()} during network bring up.
          *
          * @param mtuV4 the MTU size in bytes of the route.
          */
@@ -1799,7 +1799,7 @@
         /**
          * Set the default MTU (Maximum Transmission Unit) size in bytes of the IPv6 routes brought
          * up by this APN setting. Note this value will only be used when MTU size is not provided
-         * in {@link DataCallResponse#getMtuV6()} during network bring up.
+         * in {@code DataCallResponse#getMtuV6()} during network bring up.
          *
          * @param mtuV6 the MTU size in bytes of the route.
          */
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index ed46276..b9a7d43 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -249,7 +249,7 @@
      *
      * <p>{@link #EXTRA_USE_QR_SCANNER} not set or set to false: The LPA should try to get an
      * activation code from the carrier app by binding to the carrier app service implementing
-     * {@link android.service.euicc.EuiccService#ACTION_BIND_CARRIER_PROVISIONING_SERVICE}.
+     * {@code android.service.euicc.EuiccService#ACTION_BIND_CARRIER_PROVISIONING_SERVICE}.
      * <p>{@link #EXTRA_USE_QR_SCANNER} set to true: The LPA should launch a QR scanner for the user
      * to scan an eSIM profile QR code.
      *
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index caee4e2..2172d7d 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -561,7 +561,7 @@
      * @param c The MmTel {@link CapabilityCallback} to be registered.
      * @see #unregisterMmTelCapabilityCallback(CapabilityCallback)
      * @throws ImsException if the subscription associated with this callback is valid, but
-     * the {@link ImsService} associated with the subscription is not available. This can happen if
+     * the {@code ImsService} associated with the subscription is not available. This can happen if
      * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
      * reason.
      */
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index 4439e5c..2b49bcd 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -247,7 +247,7 @@
      * @param c The {@link RegistrationManager.RegistrationCallback} to be added.
      * @see #unregisterImsRegistrationCallback(RegistrationManager.RegistrationCallback)
      * @throws ImsException if the subscription associated with this callback is valid, but
-     * the {@link ImsService} associated with the subscription is not available. This can happen if
+     * the {@code ImsService} associated with the subscription is not available. This can happen if
      * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
      * reason.
      */
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index 37a6a7e..1c5d1e9 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -1469,9 +1469,8 @@
     /**
      * Get the provisioning status for the IMS MmTel capability specified.
      *
-     * If provisioning is not required for the queried
-     * {@link MmTelFeature.MmTelCapabilities.MmTelCapability} and
-     * {@link ImsRegistrationImplBase.ImsRegistrationTech} combination specified, this method will
+     * If provisioning is not required for the queried {@code capability} and
+     * {@code tech} combination specified, this method will
      * always return {@code true}.
      *
      * <p> Requires Permission:
@@ -1503,7 +1502,7 @@
      * Get the provisioning status for the IMS RCS capability specified.
      *
      * If provisioning is not required for the queried
-     * {@link ImsRcsManager.RcsImsCapabilityFlag} or if the device does not support IMS
+     * {@code capability} or if the device does not support IMS
      * this method will always return {@code true}.
      *
      * @see CarrierConfigManager.Ims#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL
@@ -1533,7 +1532,7 @@
      * Get the provisioning status for the IMS RCS capability specified.
      *
      * If provisioning is not required for the queried
-     * {@link ImsRcsManager.RcsImsCapabilityFlag} or if the device does not support IMS
+     * {@code capability} or if the device does not support IMS
      * this method will always return {@code true}.
      *
      * <p> Requires Permission:
diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java
index 873ce60..b528866 100644
--- a/telephony/java/android/telephony/ims/RegistrationManager.java
+++ b/telephony/java/android/telephony/ims/RegistrationManager.java
@@ -31,7 +31,6 @@
 import android.telephony.AccessNetworkConstants;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
-import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.Log;
 
@@ -42,7 +41,7 @@
 import java.util.function.Consumer;
 
 /**
- * Manages IMS Service registration state for associated {@link ImsFeature}s.
+ * Manages IMS Service registration state for associated {@code ImsFeature}s.
  */
 @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS)
 public interface RegistrationManager {
@@ -394,7 +393,7 @@
      * @param c The {@link RegistrationCallback} to be added.
      * @see #unregisterImsRegistrationCallback(RegistrationCallback)
      * @throws ImsException if the subscription associated with this callback is valid, but
-     * the {@link ImsService} associated with the subscription is not available. This can happen if
+     * the {@code ImsService} associated with the subscription is not available. This can happen if
      * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
      * reason.
      */
diff --git a/telephony/java/android/telephony/satellite/AntennaDirection.java b/telephony/java/android/telephony/satellite/AntennaDirection.java
index 22412e6..c690f98 100644
--- a/telephony/java/android/telephony/satellite/AntennaDirection.java
+++ b/telephony/java/android/telephony/satellite/AntennaDirection.java
@@ -16,11 +16,14 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.Objects;
 
 /**
@@ -38,6 +41,7 @@
  * @hide
  */
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public final class AntennaDirection implements Parcelable {
     /** Antenna x axis direction. */
     private float mX;
@@ -62,11 +66,13 @@
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public int describeContents() {
         return 0;
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeFloat(mX);
         out.writeFloat(mY);
@@ -74,6 +80,7 @@
     }
 
     @NonNull
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final Creator<AntennaDirection> CREATOR =
             new Creator<>() {
                 @Override
@@ -118,14 +125,17 @@
         return Objects.hash(mX, mY, mZ);
     }
 
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public float getX() {
         return mX;
     }
 
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public float getY() {
         return mY;
     }
 
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public float getZ() {
         return mZ;
     }
diff --git a/telephony/java/android/telephony/satellite/AntennaPosition.java b/telephony/java/android/telephony/satellite/AntennaPosition.java
index 588be6c..8842886 100644
--- a/telephony/java/android/telephony/satellite/AntennaPosition.java
+++ b/telephony/java/android/telephony/satellite/AntennaPosition.java
@@ -16,11 +16,14 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.Objects;
 
 /**
@@ -29,6 +32,7 @@
  * @hide
  */
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public final class AntennaPosition implements Parcelable {
     /** Antenna direction used for satellite communication. */
     @NonNull AntennaDirection mAntennaDirection;
@@ -49,17 +53,20 @@
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public int describeContents() {
         return 0;
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeParcelable(mAntennaDirection, flags);
         out.writeInt(mSuggestedHoldPosition);
     }
 
     @NonNull
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final Creator<AntennaPosition> CREATOR =
             new Creator<>() {
                 @Override
@@ -100,11 +107,13 @@
     }
 
     @NonNull
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public AntennaDirection getAntennaDirection() {
         return mAntennaDirection;
     }
 
     @SatelliteManager.DeviceHoldPosition
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public int getSuggestedHoldPosition() {
         return mSuggestedHoldPosition;
     }
diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java
index dc4d38b..022a856 100644
--- a/telephony/java/android/telephony/satellite/PointingInfo.java
+++ b/telephony/java/android/telephony/satellite/PointingInfo.java
@@ -16,11 +16,14 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.Objects;
 
 /**
@@ -30,6 +33,7 @@
  * @hide
  */
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public final class PointingInfo implements Parcelable {
     /** Satellite azimuth in degrees */
     private float mSatelliteAzimuthDegrees;
@@ -50,16 +54,19 @@
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public int describeContents() {
         return 0;
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeFloat(mSatelliteAzimuthDegrees);
         out.writeFloat(mSatelliteElevationDegrees);
     }
 
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final @android.annotation.NonNull Creator<PointingInfo> CREATOR =
             new Creator<PointingInfo>() {
                 @Override
@@ -101,10 +108,12 @@
         return sb.toString();
     }
 
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public float getSatelliteAzimuthDegrees() {
         return mSatelliteAzimuthDegrees;
     }
 
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public float getSatelliteElevationDegrees() {
         return mSatelliteElevationDegrees;
     }
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
index bc45be1..0d8f101 100644
--- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
@@ -16,12 +16,15 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -34,6 +37,7 @@
  * @hide
  */
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public final class SatelliteCapabilities implements Parcelable {
     /**
      * List of technologies supported by the satellite modem.
@@ -76,11 +80,13 @@
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public int describeContents() {
         return 0;
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void writeToParcel(@NonNull Parcel out, int flags) {
         if (mSupportedRadioTechnologies != null && !mSupportedRadioTechnologies.isEmpty()) {
             out.writeInt(mSupportedRadioTechnologies.size());
@@ -106,6 +112,7 @@
         }
     }
 
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     @NonNull public static final Creator<SatelliteCapabilities> CREATOR = new Creator<>() {
         @Override
         public SatelliteCapabilities createFromParcel(Parcel in) {
@@ -165,6 +172,7 @@
     /**
      * @return The list of technologies supported by the satellite modem.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     @NonNull @SatelliteManager.NTRadioTechnology public Set<Integer>
             getSupportedRadioTechnologies() {
         return mSupportedRadioTechnologies;
@@ -176,6 +184,7 @@
      * @return {@code true} if UE needs to point to a satellite to send and receive data and
      *         {@code false} otherwise.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public boolean isPointingRequired() {
         return mIsPointingRequired;
     }
@@ -185,6 +194,7 @@
      *
      * @return The maximum number of bytes per datagram that can be sent over satellite.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public int getMaxBytesPerOutgoingDatagram() {
         return mMaxBytesPerOutgoingDatagram;
     }
@@ -195,6 +205,7 @@
      * @return Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition
      */
     @NonNull
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public Map<Integer, AntennaPosition> getAntennaPositionMap() {
         return mAntennaPositionMap;
     }
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagram.java b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
index 9037f0c..4d67f80 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagram.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
@@ -16,17 +16,21 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.telephony.flags.Flags;
+
 /**
  * SatelliteDatagram is used to store data that is to be sent or received over satellite.
  * Data is stored in byte array format.
  * @hide
  */
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public final class SatelliteDatagram implements Parcelable {
     /**
      * Datagram to be sent or received over satellite.
@@ -45,15 +49,18 @@
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public int describeContents() {
         return 0;
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeByteArray(mData);
     }
 
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     @NonNull public static final Creator<SatelliteDatagram> CREATOR =
             new Creator<>() {
                 @Override
@@ -73,6 +80,7 @@
      * satellite provider. Client application should be aware of how to encode the datagram based
      * upon the satellite provider.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     @NonNull public byte[] getSatelliteDatagram() {
         return mData;
     }
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
index cb2920f..b5763c3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
@@ -16,9 +16,12 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.function.Consumer;
 
 /**
@@ -27,6 +30,7 @@
  * @hide
  */
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public interface SatelliteDatagramCallback {
     /**
      * Called when there is an incoming datagram to be received.
@@ -38,6 +42,7 @@
      *                 that they received the datagram. If the callback is not received within
      *                 five minutes, Telephony will resend the datagram.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram,
             int pendingCount, @NonNull Consumer<Void> callback);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 8dc2de8..7322aeb 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -63,6 +63,7 @@
  */
 @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SATELLITE)
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public final class SatelliteManager {
     private static final String TAG = "SatelliteManager";
 
@@ -108,6 +109,7 @@
     /**
      * Exception from the satellite service containing the {@link SatelliteResult} error code.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static class SatelliteException extends Exception {
         @SatelliteResult private final int mErrorCode;
 
@@ -116,6 +118,7 @@
          *
          * @param errorCode The {@link SatelliteResult}.
          */
+        @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
         public SatelliteException(@SatelliteResult int errorCode) {
             mErrorCode = errorCode;
         }
@@ -125,6 +128,7 @@
          *
          * @return The {@link SatelliteResult}.
          */
+        @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
         @SatelliteResult public int getErrorCode() {
             return mErrorCode;
         }
@@ -190,104 +194,127 @@
     /**
      * The request was successfully processed.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_SUCCESS = 0;
     /**
      * A generic error which should be used only when other specific errors cannot be used.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_ERROR = 1;
     /**
      * Error received from the satellite server.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_SERVER_ERROR = 2;
     /**
      * Error received from the vendor service. This generic error code should be used
      * only when the error cannot be mapped to other specific service error codes.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_SERVICE_ERROR = 3;
     /**
      * Error received from satellite modem. This generic error code should be used only when
      * the error cannot be mapped to other specific modem error codes.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_MODEM_ERROR = 4;
     /**
      * Error received from the satellite network. This generic error code should be used only when
      * the error cannot be mapped to other specific network error codes.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_NETWORK_ERROR = 5;
     /**
      * Telephony is not in a valid state to receive requests from clients.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6;
     /**
      * Satellite modem is not in a valid state to receive requests from clients.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7;
     /**
      * Either vendor service, or modem, or Telephony framework has received a request with
      * invalid arguments from its clients.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8;
     /**
      * Telephony framework failed to send a request or receive a response from the vendor service
      * or satellite modem due to internal error.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_REQUEST_FAILED = 9;
     /**
      * Radio did not start or is resetting.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10;
     /**
      * The request is not supported by either the satellite modem or the network.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_REQUEST_NOT_SUPPORTED = 11;
     /**
      * Satellite modem or network has no resources available to handle requests from clients.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_NO_RESOURCES = 12;
     /**
      * Satellite service is not provisioned yet.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_SERVICE_NOT_PROVISIONED = 13;
     /**
      * Satellite service provision is already in progress.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS = 14;
     /**
      * The ongoing request was aborted by either the satellite modem or the network.
      * This error is also returned when framework decides to abort current send request as one
      * of the previous send request failed.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15;
     /**
      * The device/subscriber is barred from accessing the satellite service.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_ACCESS_BARRED = 16;
     /**
      * Satellite modem timeout to receive ACK or response from the satellite network after
      * sending a request to the network.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17;
     /**
      * Satellite network is not reachable from the modem.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_NOT_REACHABLE = 18;
     /**
      * The device/subscriber is not authorized to register with the satellite service provider.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19;
     /**
      * The device does not support satellite.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20;
 
     /**
      * The current request is already in-progress.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_REQUEST_IN_PROGRESS = 21;
 
     /**
      * Satellite modem is currently busy due to which current request cannot be processed.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_MODEM_BUSY = 22;
 
     /** @hide */
@@ -323,22 +350,27 @@
      * Unknown Non-Terrestrial radio technology. This generic radio technology should be used
      * only when the radio technology cannot be mapped to other specific radio technologies.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0;
     /**
      * 3GPP NB-IoT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1;
     /**
      * 3GPP 5G NR over Non-Terrestrial-Networks technology.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2;
     /**
      * 3GPP eMTC (enhanced Machine-Type Communication) over Non-Terrestrial-Networks technology.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3;
     /**
      * Proprietary technology.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4;
 
     /** @hide */
@@ -353,12 +385,16 @@
     public @interface NTRadioTechnology {}
 
     /** Suggested device hold position is unknown. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0;
     /** User is suggested to hold the device in portrait mode. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1;
     /** User is suggested to hold the device in landscape mode with left hand. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2;
     /** User is suggested to hold the device in landscape mode with right hand. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3;
 
     /** @hide */
@@ -372,14 +408,18 @@
     public @interface DeviceHoldPosition {}
 
     /** Display mode is unknown. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DISPLAY_MODE_UNKNOWN = 0;
     /** Display mode of the device used for satellite communication for non-foldable phones. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DISPLAY_MODE_FIXED = 1;
     /** Display mode of the device used for satellite communication for foldabale phones when the
      * device is opened. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DISPLAY_MODE_OPENED = 2;
     /** Display mode of the device used for satellite communication for foldabable phones when the
      * device is closed. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DISPLAY_MODE_CLOSED = 3;
 
     /** @hide */
@@ -414,7 +454,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
@@ -457,7 +497,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void requestIsSatelliteEnabled(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -512,7 +552,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void requestIsDemoModeEnabled(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -565,7 +605,7 @@
      *
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void requestIsSatelliteSupported(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -619,7 +659,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void requestSatelliteCapabilities(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<SatelliteCapabilities, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -664,16 +704,19 @@
      * The default state indicating that datagram transfer is idle.
      * This should be sent if there are no message transfer activity happening.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0;
     /**
      * A transition state indicating that a datagram is being sent.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING = 1;
     /**
      * An end state indicating that datagram sending completed successfully.
      * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE}
      * will be sent if no more messages are pending.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2;
     /**
      * An end state indicating that datagram sending completed with a failure.
@@ -681,16 +724,19 @@
      * must be sent before reporting any additional datagram transfer state changes. All pending
      * messages will be reported as failed, to the corresponding applications.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3;
     /**
      * A transition state indicating that a datagram is being received.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 4;
     /**
      * An end state indicating that datagram receiving completed successfully.
      * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE}
      * will be sent if no more messages are pending.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS = 5;
     /**
      * An end state indicating that datagram receive operation found that there are no
@@ -698,12 +744,14 @@
      * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE}
      * will be sent if no more messages are pending.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6;
     /**
      * An end state indicating that datagram receive completed with a failure.
      * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE}
      * will be sent if no more messages are pending.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7;
     /**
      * A transition state indicating that Telephony is waiting for satellite modem to connect to a
@@ -721,6 +769,7 @@
      * only when the datagram transfer state cannot be mapped to other specific datagram transfer
      * states.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1;
 
     /** @hide */
@@ -743,26 +792,32 @@
     /**
      * Satellite modem is in idle state.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_MODEM_STATE_IDLE = 0;
     /**
      * Satellite modem is listening for incoming datagrams.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_MODEM_STATE_LISTENING = 1;
     /**
      * Satellite modem is sending and/or receiving datagrams.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2;
     /**
      * Satellite modem is retrying to send and/or receive datagrams.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3;
     /**
      * Satellite modem is powered off.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_MODEM_STATE_OFF = 4;
     /**
      * Satellite modem is unavailable.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5;
     /**
      * The satellite modem is powered on but the device is not registered to a satellite cell.
@@ -778,6 +833,7 @@
      * Satellite modem state is unknown. This generic modem state should be used only when the
      * modem state cannot be mapped to other specific modem states.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1;
 
     /** @hide */
@@ -799,15 +855,18 @@
      * Datagram type is unknown. This generic datagram type should be used only when the
      * datagram type cannot be mapped to other specific datagram types.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DATAGRAM_TYPE_UNKNOWN = 0;
     /**
      * Datagram type indicating that the datagram to be sent or received is of type SOS message.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1;
     /**
      * Datagram type indicating that the datagram to be sent or received is of type
      * location sharing.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2;
 
     /** @hide */
@@ -857,7 +916,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void startSatelliteTransmissionUpdates(@NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener,
             @NonNull SatelliteTransmissionUpdateCallback callback) {
@@ -927,7 +986,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void stopSatelliteTransmissionUpdates(
             @NonNull SatelliteTransmissionUpdateCallback callback,
             @NonNull @CallbackExecutor Executor executor,
@@ -983,7 +1042,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
             @Nullable CancellationSignal cancellationSignal,
             @NonNull @CallbackExecutor Executor executor,
@@ -1036,7 +1095,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void deprovisionSatelliteService(@NonNull String token,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
@@ -1076,7 +1135,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     @SatelliteResult public int registerForSatelliteProvisionStateChanged(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteProvisionStateCallback callback) {
@@ -1119,7 +1178,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void unregisterForSatelliteProvisionStateChanged(
             @NonNull SatelliteProvisionStateCallback callback) {
         Objects.requireNonNull(callback);
@@ -1158,7 +1217,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void requestIsSatelliteProvisioned(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -1210,7 +1269,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     @SatelliteResult public int registerForSatelliteModemStateChanged(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteStateCallback callback) {
@@ -1250,7 +1309,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void unregisterForSatelliteModemStateChanged(@NonNull SatelliteStateCallback callback) {
         Objects.requireNonNull(callback);
         ISatelliteStateCallback internalCallback = sSatelliteStateCallbackMap.remove(callback);
@@ -1288,7 +1347,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     @SatelliteResult public int registerForSatelliteDatagram(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteDatagramCallback callback) {
@@ -1344,7 +1403,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void unregisterForSatelliteDatagram(@NonNull SatelliteDatagramCallback callback) {
         Objects.requireNonNull(callback);
         ISatelliteDatagramCallback internalCallback =
@@ -1383,7 +1442,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void pollPendingSatelliteDatagrams(@NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(executor);
@@ -1436,7 +1495,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void sendSatelliteDatagram(@DatagramType int datagramType,
             @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
             @NonNull @CallbackExecutor Executor executor,
@@ -1482,7 +1541,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void requestIsSatelliteCommunicationAllowedForCurrentLocation(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
@@ -1540,7 +1599,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void requestTimeForNextSatelliteVisibility(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Duration, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -1594,7 +1653,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void setDeviceAlignedWithSatellite(boolean isAligned) {
         try {
             ITelephony telephony = getITelephony();
@@ -1628,7 +1687,7 @@
      * @param subId The subscription ID of the carrier.
      * @param enableSatellite {@code true} to enable the satellite and {@code false} to disable.
      * @param executor The executor on which the error code listener will be called.
-     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -1662,7 +1721,7 @@
      *                 will return a {@code boolean} with value {@code true} if the satellite
      *                 is enabled and {@code false} otherwise.
      *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
-     *                 will return a {@link SatelliteException} with the {@link SatelliteError}.
+     *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -1688,7 +1747,7 @@
      * @param subId The subscription ID of the carrier.
      * @param reason Reason for disallowing satellite communication.
      * @param executor The executor on which the error code listener will be called.
-     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -1731,7 +1790,7 @@
      * @param subId The subscription ID of the carrier.
      * @param reason Reason for disallowing satellite communication.
      * @param executor The executor on which the error code listener will be called.
-     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
index 7116876..a12952b 100644
--- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
@@ -16,14 +16,18 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.SystemApi;
 
+import com.android.internal.telephony.flags.Flags;
+
 /**
  * A callback class for monitoring satellite provision state change events.
  *
  * @hide
  */
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public interface SatelliteProvisionStateCallback {
     /**
      * Called when satellite provision state changes.
@@ -33,5 +37,6 @@
      *                    It is generally expected that the provisioning app retries if
      *                    provisioning fails.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     void onSatelliteProvisionStateChanged(boolean provisioned);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
index 812ff2d..bfe6e10 100644
--- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
@@ -16,18 +16,23 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.SystemApi;
 
+import com.android.internal.telephony.flags.Flags;
+
 /**
  * A callback class for monitoring satellite modem state change events.
  *
  * @hide
  */
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public interface SatelliteStateCallback {
     /**
      * Called when satellite modem state changes.
      * @param state The new satellite modem state.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index 7ac06b0..e020970 100644
--- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -16,9 +16,12 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 
+import com.android.internal.telephony.flags.Flags;
+
 /**
  * A callback class for monitoring satellite position update and datagram transfer state change
  * events.
@@ -26,12 +29,14 @@
  * @hide
  */
 @SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public interface SatelliteTransmissionUpdateCallback {
     /**
      * Called when the satellite position changed.
      *
      * @param pointingInfo The pointing info containing the satellite location.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo);
 
     /**
@@ -41,6 +46,7 @@
      * @param sendPendingCount The number of datagrams that are currently being sent.
      * @param errorCode If datagram transfer failed, the reason for failure.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     void onSendDatagramStateChanged(
             @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
             @SatelliteManager.SatelliteResult int errorCode);
@@ -52,6 +58,7 @@
      * @param receivePendingCount The number of datagrams that are currently pending to be received.
      * @param errorCode If datagram transfer failed, the reason for failure.
      */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     void onReceiveDatagramStateChanged(
             @SatelliteManager.SatelliteDatagramTransferState int state, int receivePendingCount,
             @SatelliteManager.SatelliteResult int errorCode);
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 0fcd0d6..7fda550 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -32,15 +32,15 @@
      *
      * @param listener The callback interface to handle satellite service indications.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void setSatelliteListener(in ISatelliteListener listener);
 
@@ -53,15 +53,15 @@
      *                disabling listening mode.
      * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void requestSatelliteListeningEnabled(in boolean enable, in int timeout,
             in IIntegerConsumer resultCallback);
@@ -84,15 +84,15 @@
      * @param enableDemoMode True to enable demo mode and false to disable.
      * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void requestSatelliteEnabled(in boolean enableSatellite, in boolean enableDemoMode,
             in IIntegerConsumer resultCallback);
@@ -101,39 +101,42 @@
      * Request to get whether the satellite modem is enabled.
      *
      * @param resultCallback The callback to receive the error code result of the operation.
-     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 whether the satellite modem is enabled.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive whether the satellite modem is enabled.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    void requestIsSatelliteEnabled(in IIntegerConsumer resultCallback, in IBooleanConsumer callback);
+    void requestIsSatelliteEnabled(in IIntegerConsumer resultCallback,
+            in IBooleanConsumer callback);
 
     /**
      * Request to get whether the satellite service is supported on the device.
      *
      * @param resultCallback The callback to receive the error code result of the operation.
-     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 whether the satellite service is supported on the device.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive whether the satellite service is supported on the device.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void requestIsSatelliteSupported(in IIntegerConsumer resultCallback,
             in IBooleanConsumer callback);
@@ -142,19 +145,20 @@
      * Request to get the SatelliteCapabilities of the satellite service.
      *
      * @param resultCallback The callback to receive the error code result of the operation.
-     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 the SatelliteCapabilities of the satellite service.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive the SatelliteCapabilities of the satellite service.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void requestSatelliteCapabilities(in IIntegerConsumer resultCallback,
             in ISatelliteCapabilitiesConsumer callback);
@@ -166,15 +170,15 @@
      *
      * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void startSendingSatellitePointingInfo(in IIntegerConsumer resultCallback);
 
@@ -184,15 +188,15 @@
      *
      * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void stopSendingSatellitePointingInfo(in IIntegerConsumer resultCallback);
 
@@ -206,18 +210,18 @@
      * @param provisionData Data from the provisioning app that can be used by provisioning server
      * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:NETWORK_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:REQUEST_ABORTED
-     *   SatelliteError:NETWORK_TIMEOUT
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
      */
     void provisionSatelliteService(in String token, in byte[] provisionData,
             in IIntegerConsumer resultCallback);
@@ -230,18 +234,18 @@
      * @param token The token of the device/subscription to be deprovisioned.
      * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:NETWORK_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:REQUEST_ABORTED
-     *   SatelliteError:NETWORK_TIMEOUT
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
      */
     void deprovisionSatelliteService(in String token, in IIntegerConsumer resultCallback);
 
@@ -249,19 +253,20 @@
      * Request to get whether this device is provisioned with a satellite provider.
      *
      * @param resultCallback The callback to receive the error code result of the operation.
-     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 whether this device is provisioned with a satellite provider.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive whether this device is provisioned with a satellite provider.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void requestIsSatelliteProvisioned(in IIntegerConsumer resultCallback,
             in IBooleanConsumer callback);
@@ -273,20 +278,20 @@
      *
      * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:NETWORK_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:SATELLITE_ACCESS_BARRED
-     *   SatelliteError:NETWORK_TIMEOUT
-     *   SatelliteError:SATELLITE_NOT_REACHABLE
-     *   SatelliteError:NOT_AUTHORIZED
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_ACCESS_BARRED
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
+     *   SatelliteResult:SATELLITE_RESULT_NOT_REACHABLE
+     *   SatelliteResult:SATELLITE_RESULT_NOT_AUTHORIZED
      */
     void pollPendingSatelliteDatagrams(in IIntegerConsumer resultCallback);
 
@@ -297,21 +302,21 @@
      * @param isEmergency Whether this is an emergency datagram.
      * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:NETWORK_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:REQUEST_ABORTED
-     *   SatelliteError:SATELLITE_ACCESS_BARRED
-     *   SatelliteError:NETWORK_TIMEOUT
-     *   SatelliteError:SATELLITE_NOT_REACHABLE
-     *   SatelliteError:NOT_AUTHORIZED
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
+     *   SatelliteResult:SATELLITE_RESULT_ACCESS_BARRED
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
+     *   SatelliteResult:SATELLITE_RESULT_NOT_REACHABLE
+     *   SatelliteResult:SATELLITE_RESULT_NOT_AUTHORIZED
      */
     void sendSatelliteDatagram(in SatelliteDatagram datagram, in boolean isEmergency,
             in IIntegerConsumer resultCallback);
@@ -322,19 +327,20 @@
      * ISatelliteListener#onSatelliteModemStateChanged.
      *
      * @param resultCallback The callback to receive the error code result of the operation.
-     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 the current satellite modem state.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive the current satellite modem state.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void requestSatelliteModemState(in IIntegerConsumer resultCallback,
             in IIntegerConsumer callback);
@@ -343,19 +349,20 @@
      * Request to get whether satellite communication is allowed for the current location.
      *
      * @param resultCallback The callback to receive the error code result of the operation.
-     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 whether satellite communication is allowed for the current location.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive whether satellite communication is allowed for the current location.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void requestIsSatelliteCommunicationAllowedForCurrentLocation(
             in IIntegerConsumer resultCallback, in IBooleanConsumer callback);
@@ -366,19 +373,20 @@
      * This will return 0 if the satellite is currently visible.
      *
      * @param resultCallback The callback to receive the error code result of the operation.
-     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 the time after which the satellite will be visible.
+     *                       This must only be sent when the result is not
+          *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive the time after which the satellite will be visible.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     void requestTimeForNextSatelliteVisibility(in IIntegerConsumer resultCallback,
             in IIntegerConsumer callback);
@@ -399,14 +407,14 @@
      *                             attach to them.
      * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:NONE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:MODEM_ERR
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
+     * Valid result codes returned:
+     *   SatelliteResult:NONE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:MODEM_ERR
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
      */
     void setSatellitePlmn(int simSlot, in List<String> carrierPlmnList,
             in List<String> allSatellitePlmnList, in IIntegerConsumer resultCallback);
@@ -420,12 +428,12 @@
      * @param serial Serial number of request.
      * @param enable {@code true} to enable satellite, {@code false} to disable satellite.
      *
-     * Valid errors returned:
-     *   SatelliteError:NONE
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:MODEM_ERR
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
      */
     void setSatelliteEnabledForCarrier(int simSlot, boolean satelliteEnabled,
          in IIntegerConsumer callback);
@@ -437,12 +445,12 @@
      *                this information to determine the relevant carrier.
      * @param serial Serial number of request.
      *
-     * Valid errors returned:
-     *   SatelliteError:NONE
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:MODEM_ERR
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
      */
     void requestIsSatelliteEnabledForCarrier(int simSlot, in IIntegerConsumer resultCallback,
             in IBooleanConsumer callback);
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index a9c09c9..4cee01e 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -72,172 +72,172 @@
 
         @Override
         public void requestSatelliteListeningEnabled(boolean enable, int timeout,
-                IIntegerConsumer errorCallback) throws RemoteException {
+                IIntegerConsumer resultCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestSatelliteListeningEnabled(enable, timeout, errorCallback),
+                            .requestSatelliteListeningEnabled(enable, timeout, resultCallback),
                     "requestSatelliteListeningEnabled");
         }
 
         @Override
         public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled,
-                IIntegerConsumer errorCallback) throws RemoteException {
+                IIntegerConsumer resultCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .enableCellularModemWhileSatelliteModeIsOn(enabled, errorCallback),
+                            .enableCellularModemWhileSatelliteModeIsOn(enabled, resultCallback),
                     "enableCellularModemWhileSatelliteModeIsOn");
         }
 
         @Override
         public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
-                IIntegerConsumer errorCallback) throws RemoteException {
+                IIntegerConsumer resultCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
                             .requestSatelliteEnabled(
-                                    enableSatellite, enableDemoMode, errorCallback),
+                                    enableSatellite, enableDemoMode, resultCallback),
                     "requestSatelliteEnabled");
         }
 
         @Override
-        public void requestIsSatelliteEnabled(IIntegerConsumer errorCallback,
+        public void requestIsSatelliteEnabled(IIntegerConsumer resultCallback,
                 IBooleanConsumer callback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestIsSatelliteEnabled(errorCallback, callback),
+                            .requestIsSatelliteEnabled(resultCallback, callback),
                     "requestIsSatelliteEnabled");
         }
 
         @Override
-        public void requestIsSatelliteSupported(IIntegerConsumer errorCallback,
+        public void requestIsSatelliteSupported(IIntegerConsumer resultCallback,
                 IBooleanConsumer callback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestIsSatelliteSupported(errorCallback, callback),
+                            .requestIsSatelliteSupported(resultCallback, callback),
                     "requestIsSatelliteSupported");
         }
 
         @Override
-        public void requestSatelliteCapabilities(IIntegerConsumer errorCallback,
+        public void requestSatelliteCapabilities(IIntegerConsumer resultCallback,
                 ISatelliteCapabilitiesConsumer callback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestSatelliteCapabilities(errorCallback, callback),
+                            .requestSatelliteCapabilities(resultCallback, callback),
                     "requestSatelliteCapabilities");
         }
 
         @Override
-        public void startSendingSatellitePointingInfo(IIntegerConsumer errorCallback)
+        public void startSendingSatellitePointingInfo(IIntegerConsumer resultCallback)
                 throws RemoteException {
             executeMethodAsync(
-                    () -> SatelliteImplBase.this.startSendingSatellitePointingInfo(errorCallback),
+                    () -> SatelliteImplBase.this.startSendingSatellitePointingInfo(resultCallback),
                     "startSendingSatellitePointingInfo");
         }
 
         @Override
-        public void stopSendingSatellitePointingInfo(IIntegerConsumer errorCallback)
+        public void stopSendingSatellitePointingInfo(IIntegerConsumer resultCallback)
                 throws RemoteException {
             executeMethodAsync(
-                    () -> SatelliteImplBase.this.stopSendingSatellitePointingInfo(errorCallback),
+                    () -> SatelliteImplBase.this.stopSendingSatellitePointingInfo(resultCallback),
                     "stopSendingSatellitePointingInfo");
         }
 
         @Override
         public void provisionSatelliteService(String token, byte[] provisionData,
-                IIntegerConsumer errorCallback) throws RemoteException {
+                IIntegerConsumer resultCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .provisionSatelliteService(token, provisionData, errorCallback),
+                            .provisionSatelliteService(token, provisionData, resultCallback),
                     "provisionSatelliteService");
         }
 
         @Override
-        public void deprovisionSatelliteService(String token, IIntegerConsumer errorCallback)
+        public void deprovisionSatelliteService(String token, IIntegerConsumer resultCallback)
                 throws RemoteException {
             executeMethodAsync(
-                    () -> SatelliteImplBase.this.deprovisionSatelliteService(token, errorCallback),
+                    () -> SatelliteImplBase.this.deprovisionSatelliteService(token, resultCallback),
                     "deprovisionSatelliteService");
         }
 
         @Override
-        public void requestIsSatelliteProvisioned(IIntegerConsumer errorCallback,
+        public void requestIsSatelliteProvisioned(IIntegerConsumer resultCallback,
                 IBooleanConsumer callback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestIsSatelliteProvisioned(errorCallback, callback),
+                            .requestIsSatelliteProvisioned(resultCallback, callback),
                     "requestIsSatelliteProvisioned");
         }
 
         @Override
-        public void pollPendingSatelliteDatagrams(IIntegerConsumer errorCallback)
+        public void pollPendingSatelliteDatagrams(IIntegerConsumer resultCallback)
                 throws RemoteException {
             executeMethodAsync(
-                    () -> SatelliteImplBase.this.pollPendingSatelliteDatagrams(errorCallback),
+                    () -> SatelliteImplBase.this.pollPendingSatelliteDatagrams(resultCallback),
                     "pollPendingSatelliteDatagrams");
         }
 
         @Override
         public void sendSatelliteDatagram(SatelliteDatagram datagram, boolean isEmergency,
-                IIntegerConsumer errorCallback) throws RemoteException {
+                IIntegerConsumer resultCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .sendSatelliteDatagram(datagram, isEmergency, errorCallback),
+                            .sendSatelliteDatagram(datagram, isEmergency, resultCallback),
                     "sendSatelliteDatagram");
         }
 
         @Override
-        public void requestSatelliteModemState(IIntegerConsumer errorCallback,
+        public void requestSatelliteModemState(IIntegerConsumer resultCallback,
                 IIntegerConsumer callback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestSatelliteModemState(errorCallback, callback),
+                            .requestSatelliteModemState(resultCallback, callback),
                     "requestSatelliteModemState");
         }
 
         @Override
         public void requestIsSatelliteCommunicationAllowedForCurrentLocation(
-                IIntegerConsumer errorCallback, IBooleanConsumer callback)
+                IIntegerConsumer resultCallback, IBooleanConsumer callback)
                 throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
                             .requestIsSatelliteCommunicationAllowedForCurrentLocation(
-                                    errorCallback, callback),
+                                    resultCallback, callback),
                     "requestIsSatelliteCommunicationAllowedForCurrentLocation");
         }
 
         @Override
-        public void requestTimeForNextSatelliteVisibility(IIntegerConsumer errorCallback,
+        public void requestTimeForNextSatelliteVisibility(IIntegerConsumer resultCallback,
                 IIntegerConsumer callback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestTimeForNextSatelliteVisibility(errorCallback, callback),
+                            .requestTimeForNextSatelliteVisibility(resultCallback, callback),
                     "requestTimeForNextSatelliteVisibility");
         }
 
         @Override
         public void setSatellitePlmn(int simSlot, List<String> carrierPlmnList,
-                List<String> devicePlmnList, IIntegerConsumer errorCallback)
+                List<String> devicePlmnList, IIntegerConsumer resultCallback)
                 throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this.setSatellitePlmn(
-                            simSlot, carrierPlmnList, devicePlmnList, errorCallback),
+                            simSlot, carrierPlmnList, devicePlmnList, resultCallback),
                     "setSatellitePlmn");
         }
 
         @Override
         public void setSatelliteEnabledForCarrier(int simSlot, boolean enableSatellite,
-                IIntegerConsumer errorCallback) throws RemoteException {
+                IIntegerConsumer resultCallback) throws RemoteException {
             executeMethodAsync(
-                    () -> SatelliteImplBase.this
-                            .setSatelliteEnabledForCarrier(simSlot, enableSatellite, errorCallback),
+                    () -> SatelliteImplBase.this.setSatelliteEnabledForCarrier(
+                            simSlot, enableSatellite, resultCallback),
                     "setSatelliteEnabledForCarrier");
         }
 
         @Override
-        public void requestIsSatelliteEnabledForCarrier(int simSlot, IIntegerConsumer errorCallback,
-                IBooleanConsumer callback) throws RemoteException {
+        public void requestIsSatelliteEnabledForCarrier(int simSlot,
+                IIntegerConsumer resultCallback, IBooleanConsumer callback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestIsSatelliteEnabledForCarrier(simSlot, errorCallback, callback),
+                            .requestIsSatelliteEnabledForCarrier(simSlot, resultCallback, callback),
                     "requestIsSatelliteEnabledForCarrier");
         }
 
@@ -260,15 +260,15 @@
      *
      * @param listener The callback interface to handle satellite service indications.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     public void setSatelliteListener(@NonNull ISatelliteListener listener) {
         // stub implementation
@@ -281,20 +281,20 @@
      * @param enable True to enable satellite listening mode and false to disable.
      * @param timeout How long the satellite modem should wait for the next incoming page before
      *                disabling listening mode.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     public void requestSatelliteListeningEnabled(boolean enable, int timeout,
-            @NonNull IIntegerConsumer errorCallback) {
+            @NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
@@ -302,10 +302,10 @@
      * Allow cellular modem scanning while satellite mode is on.
      * @param enabled  {@code true} to enable cellular modem while satellite mode is on
      * and {@code false} to disable
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      */
     public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled,
-            @NonNull IIntegerConsumer errorCallback) {
+            @NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
@@ -316,42 +316,43 @@
      *
      * @param enableSatellite True to enable the satellite modem and false to disable.
      * @param enableDemoMode True to enable demo mode and false to disable.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
-            @NonNull IIntegerConsumer errorCallback) {
+            @NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
     /**
      * Request to get whether the satellite modem is enabled.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 whether the satellite modem is enabled.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive whether the satellite modem is enabled.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    public void requestIsSatelliteEnabled(@NonNull IIntegerConsumer errorCallback,
+    public void requestIsSatelliteEnabled(@NonNull IIntegerConsumer resultCallback,
             @NonNull IBooleanConsumer callback) {
         // stub implementation
     }
@@ -359,22 +360,23 @@
     /**
      * Request to get whether the satellite service is supported on the device.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 whether the satellite service is supported on the device.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive whether the satellite service is supported on the device.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    public void requestIsSatelliteSupported(@NonNull IIntegerConsumer errorCallback,
+    public void requestIsSatelliteSupported(@NonNull IIntegerConsumer resultCallback,
             @NonNull IBooleanConsumer callback) {
         // stub implementation
     }
@@ -382,22 +384,23 @@
     /**
      * Request to get the SatelliteCapabilities of the satellite service.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 the SatelliteCapabilities of the satellite service.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive the SatelliteCapabilities of the satellite service.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    public void requestSatelliteCapabilities(@NonNull IIntegerConsumer errorCallback,
+    public void requestSatelliteCapabilities(@NonNull IIntegerConsumer resultCallback,
             @NonNull ISatelliteCapabilitiesConsumer callback) {
         // stub implementation
     }
@@ -407,19 +410,19 @@
      * The satellite service should report the satellite pointing info via
      * ISatelliteListener#onSatellitePositionChanged as the user device/satellite moves.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    public void startSendingSatellitePointingInfo(@NonNull IIntegerConsumer errorCallback) {
+    public void startSendingSatellitePointingInfo(@NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
@@ -427,19 +430,19 @@
      * User stopped pointing to the satellite.
      * The satellite service should stop reporting satellite pointing info to the framework.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    public void stopSendingSatellitePointingInfo(@NonNull IIntegerConsumer errorCallback) {
+    public void stopSendingSatellitePointingInfo(@NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
@@ -452,23 +455,23 @@
      *              gateway.
      * @param provisionData Data from the provisioning app that can be used by provisioning
      *                      server
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:NETWORK_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:REQUEST_ABORTED
-     *   SatelliteError:NETWORK_TIMEOUT
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
      */
     public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
-            @NonNull IIntegerConsumer errorCallback) {
+            @NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
@@ -478,45 +481,46 @@
      * Once deprovisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report false.
      *
      * @param token The token of the device/subscription to be deprovisioned.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:NETWORK_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:REQUEST_ABORTED
-     *   SatelliteError:NETWORK_TIMEOUT
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
      */
     public void deprovisionSatelliteService(@NonNull String token,
-            @NonNull IIntegerConsumer errorCallback) {
+            @NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
     /**
      * Request to get whether this device is provisioned with a satellite provider.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 whether this device is provisioned with a satellite provider.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive whether this device is provisioned with a satellite provider.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    public void requestIsSatelliteProvisioned(@NonNull IIntegerConsumer errorCallback,
+    public void requestIsSatelliteProvisioned(@NonNull IIntegerConsumer resultCallback,
             @NonNull IBooleanConsumer callback) {
         // stub implementation
     }
@@ -526,24 +530,24 @@
      * The satellite service should check if there are any pending datagrams to be received over
      * satellite and report them via ISatelliteListener#onSatelliteDatagramsReceived.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:NETWORK_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:SATELLITE_ACCESS_BARRED
-     *   SatelliteError:NETWORK_TIMEOUT
-     *   SatelliteError:SATELLITE_NOT_REACHABLE
-     *   SatelliteError:NOT_AUTHORIZED
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_ACCESS_BARRED
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
+     *   SatelliteResult:SATELLITE_RESULT_NOT_REACHABLE
+     *   SatelliteResult:SATELLITE_RESULT_NOT_AUTHORIZED
      */
-    public void pollPendingSatelliteDatagrams(@NonNull IIntegerConsumer errorCallback) {
+    public void pollPendingSatelliteDatagrams(@NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
@@ -552,26 +556,26 @@
      *
      * @param datagram Datagram to send in byte format.
      * @param isEmergency Whether this is an emergency datagram.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:NETWORK_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:REQUEST_ABORTED
-     *   SatelliteError:SATELLITE_ACCESS_BARRED
-     *   SatelliteError:NETWORK_TIMEOUT
-     *   SatelliteError:SATELLITE_NOT_REACHABLE
-     *   SatelliteError:NOT_AUTHORIZED
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
+     *   SatelliteResult:SATELLITE_RESULT_ACCESS_BARRED
+     *   SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
+     *   SatelliteResult:SATELLITE_RESULT_NOT_REACHABLE
+     *   SatelliteResult:SATELLITE_RESULT_NOT_AUTHORIZED
      */
     public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isEmergency,
-            @NonNull IIntegerConsumer errorCallback) {
+            @NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
@@ -580,22 +584,23 @@
      * The satellite service should report the current satellite modem state via
      * ISatelliteListener#onSatelliteModemStateChanged.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 the current satellite modem state.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive the current satellite modem state.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    public void requestSatelliteModemState(@NonNull IIntegerConsumer errorCallback,
+    public void requestSatelliteModemState(@NonNull IIntegerConsumer resultCallback,
             @NonNull IIntegerConsumer callback) {
         // stub implementation
     }
@@ -603,23 +608,24 @@
     /**
      * Request to get whether satellite communication is allowed for the current location.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 whether satellite communication is allowed for the current location.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive whether satellite communication is allowed for the current location.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
     public void requestIsSatelliteCommunicationAllowedForCurrentLocation(
-            @NonNull IIntegerConsumer errorCallback, @NonNull IBooleanConsumer callback) {
+            @NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) {
         // stub implementation
     }
 
@@ -628,22 +634,23 @@
      * representing the duration in seconds after which the satellite will be visible.
      * This will return 0 if the satellite is currently visible.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 the time after which the satellite will be visible.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the result is not
+     *                       SatelliteResult#SATELLITE_RESULT_SUCCESS.
+     * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
+     *                 receive the time after which the satellite will be visible.
      *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    public void requestTimeForNextSatelliteVisibility(@NonNull IIntegerConsumer errorCallback,
+    public void requestTimeForNextSatelliteVisibility(@NonNull IIntegerConsumer resultCallback,
             @NonNull IIntegerConsumer callback) {
         // stub implementation
     }
@@ -658,25 +665,25 @@
      *
      * @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be
      * applied. The modem will use this information to determine the relevant carrier.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      * @param carrierPlmnList The list of roaming PLMN used for connecting to satellite networks
      *                        supported by user subscription.
      * @param allSatellitePlmnList Modem should use the allSatellitePlmnList to identify satellite
      *                             PLMNs that are not supported by the carrier and make sure not to
      *                             attach to them.
      *
-     * Valid error codes returned:
-     *   SatelliteError:NONE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:MODEM_ERR
-     *   SatelliteError:NO_RESOURCES
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
+     * Valid result codes returned:
+     *   SatelliteResult:NONE
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:MODEM_ERR
+     *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
      */
     public void setSatellitePlmn(@NonNull int simLogicalSlotIndex,
             @NonNull List<String> carrierPlmnList, @NonNull List<String> allSatellitePlmnList,
-            @NonNull IIntegerConsumer errorCallback) {
+            @NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
@@ -689,12 +696,12 @@
      * @param satelliteEnabled {@code true} to enable satellite, {@code false} to disable satellite.
      * @param callback {@code true} to enable satellite, {@code false} to disable satellite.
      *
-     * Valid errors returned:
-     *   SatelliteError:NONE
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:MODEM_ERR
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
      */
     public void setSatelliteEnabledForCarrier(@NonNull int simLogicalSlotIndex,
             @NonNull boolean satelliteEnabled, @NonNull IIntegerConsumer callback) {
@@ -707,18 +714,18 @@
      *
      * @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be
      * applied. The modem will use this information to determine the relevant carrier.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      * @param callback {@code true} to satellite enabled, {@code false} to satellite disabled.
      *
-     * Valid errors returned:
-     *   SatelliteError:NONE
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:MODEM_ERR
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
      */
     public void requestIsSatelliteEnabledForCarrier(@NonNull int simLogicalSlotIndex,
-            @NonNull IIntegerConsumer errorCallback, @NonNull IBooleanConsumer callback) {
+            @NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) {
         // stub implementation
     }
 }
diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt
index d1a68d4..f61cce6 100644
--- a/test-mock/api/current.txt
+++ b/test-mock/api/current.txt
@@ -200,7 +200,7 @@
     method @Deprecated public int checkPermission(String, String);
     method @Deprecated public int checkSignatures(String, String);
     method @Deprecated public int checkSignatures(int, int);
-    method public void clearInstantAppCookie();
+    method @Deprecated public void clearInstantAppCookie();
     method @Deprecated public void clearPackagePreferredActivities(String);
     method @Deprecated public String[] currentToCanonicalPackageNames(String[]);
     method @Deprecated public void extendVerificationTimeout(int, int, long);
@@ -222,15 +222,15 @@
     method @Deprecated public CharSequence getApplicationLabel(android.content.pm.ApplicationInfo);
     method @Deprecated public android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
     method @Deprecated public android.graphics.drawable.Drawable getApplicationLogo(String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.ChangedPackages getChangedPackages(int);
+    method @Deprecated public android.content.pm.ChangedPackages getChangedPackages(int);
     method @Deprecated public int getComponentEnabledSetting(android.content.ComponentName);
     method @Deprecated public android.graphics.drawable.Drawable getDefaultActivityIcon();
     method @Deprecated public android.graphics.drawable.Drawable getDrawable(String, int, android.content.pm.ApplicationInfo);
     method @Deprecated public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
     method @Deprecated public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
     method @Deprecated public String getInstallerPackageName(String);
-    method public byte[] getInstantAppCookie();
-    method public int getInstantAppCookieMaxBytes();
+    method @Deprecated public byte[] getInstantAppCookie();
+    method @Deprecated public int getInstantAppCookieMaxBytes();
     method @Deprecated public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public android.content.Intent getLaunchIntentForPackage(String);
     method @Deprecated public android.content.Intent getLeanbackLaunchIntentForPackage(String);
@@ -239,7 +239,7 @@
     method @Deprecated public int[] getPackageGids(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public android.content.pm.PackageInfo getPackageInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.PackageInstaller getPackageInstaller();
+    method @Deprecated public android.content.pm.PackageInstaller getPackageInstaller();
     method @Deprecated public int getPackageUid(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public String[] getPackagesForUid(int);
     method @Deprecated public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(String[], int);
@@ -263,8 +263,8 @@
     method @Deprecated public android.content.res.XmlResourceParser getXml(String, int, android.content.pm.ApplicationInfo);
     method @Deprecated public boolean hasSystemFeature(String);
     method @Deprecated public boolean hasSystemFeature(String, int);
-    method public boolean isInstantApp();
-    method public boolean isInstantApp(String);
+    method @Deprecated public boolean isInstantApp();
+    method @Deprecated public boolean isInstantApp(String);
     method @Deprecated public boolean isPermissionRevokedByPolicy(String, String);
     method @Deprecated public boolean isSafeMode();
     method @Deprecated public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -281,11 +281,12 @@
     method @Deprecated public android.content.pm.ProviderInfo resolveContentProvider(String, int);
     method @Deprecated public android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
     method @Deprecated public android.content.pm.ResolveInfo resolveServiceAsUser(android.content.Intent, int, int);
-    method public void setApplicationCategoryHint(String, int);
+    method @Deprecated public void setApplicationCategoryHint(String, int);
     method @Deprecated public void setApplicationEnabledSetting(String, int, int);
     method @Deprecated public void setComponentEnabledSetting(android.content.ComponentName, int, int);
     method @Deprecated public void setInstallerPackageName(String, String);
-    method public void updateInstantAppCookie(@NonNull byte[]);
+    method @Deprecated public boolean setInstantAppCookie(@NonNull byte[]);
+    method @Deprecated public void updateInstantAppCookie(@NonNull byte[]);
     method @Deprecated public void verifyPendingInstall(int, int);
   }
 
diff --git a/test-mock/api/removed.txt b/test-mock/api/removed.txt
index 1496c35..0c800b6 100644
--- a/test-mock/api/removed.txt
+++ b/test-mock/api/removed.txt
@@ -9,7 +9,7 @@
   @Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
     method @Deprecated public String getDefaultBrowserPackageName(int);
     method @Deprecated public boolean setDefaultBrowserPackageName(String, int);
-    method public boolean setInstantAppCookie(@NonNull byte[]);
+    method @Deprecated public boolean setInstantAppCookie(@NonNull byte[]);
   }
 
 }
diff --git a/test-mock/api/system-current.txt b/test-mock/api/system-current.txt
index 9e022f0..f350957 100644
--- a/test-mock/api/system-current.txt
+++ b/test-mock/api/system-current.txt
@@ -9,30 +9,30 @@
   }
 
   @Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
-    method public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
-    method public boolean arePermissionsIndividuallyControlled();
+    method @Deprecated public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+    method @Deprecated public boolean arePermissionsIndividuallyControlled();
     method @Deprecated public java.util.List<android.content.IntentFilter> getAllIntentFilters(String);
-    method public String getDefaultBrowserPackageNameAsUser(int);
-    method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
-    method public android.graphics.drawable.Drawable getInstantAppIcon(String);
-    method public android.content.ComponentName getInstantAppInstallerComponent();
-    method public android.content.ComponentName getInstantAppResolverSettingsComponent();
-    method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
-    method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(String);
-    method public int getIntentVerificationStatusAsUser(String, int);
-    method public int getPermissionFlags(String, String, android.os.UserHandle);
-    method public void grantRuntimePermission(String, String, android.os.UserHandle);
-    method public int installExistingPackage(String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public int installExistingPackage(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public void registerDexModule(String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback);
-    method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
-    method public void revokeRuntimePermission(String, String, android.os.UserHandle);
-    method public boolean setDefaultBrowserPackageNameAsUser(String, int);
+    method @Deprecated public String getDefaultBrowserPackageNameAsUser(int);
+    method @Deprecated public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
+    method @Deprecated public android.graphics.drawable.Drawable getInstantAppIcon(String);
+    method @Deprecated public android.content.ComponentName getInstantAppInstallerComponent();
+    method @Deprecated public android.content.ComponentName getInstantAppResolverSettingsComponent();
+    method @Deprecated public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
+    method @Deprecated public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(String);
+    method @Deprecated public int getIntentVerificationStatusAsUser(String, int);
+    method @Deprecated public int getPermissionFlags(String, String, android.os.UserHandle);
+    method @Deprecated public void grantRuntimePermission(String, String, android.os.UserHandle);
+    method @Deprecated public int installExistingPackage(String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated public int installExistingPackage(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated public void registerDexModule(String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback);
+    method @Deprecated public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+    method @Deprecated public void revokeRuntimePermission(String, String, android.os.UserHandle);
+    method @Deprecated public boolean setDefaultBrowserPackageNameAsUser(String, int);
     method public String[] setPackagesSuspended(String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, String);
-    method public void setUpdateAvailable(String, boolean);
-    method public boolean updateIntentVerificationStatusAsUser(String, int, int);
-    method public void updatePermissionFlags(String, String, int, int, android.os.UserHandle);
-    method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
+    method @Deprecated public void setUpdateAvailable(String, boolean);
+    method @Deprecated public boolean updateIntentVerificationStatusAsUser(String, int, int);
+    method @Deprecated public void updatePermissionFlags(String, String, int, int, android.os.UserHandle);
+    method @Deprecated public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
   }
 
 }
diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt
index 35f076f..9ed0108 100644
--- a/test-mock/api/test-current.txt
+++ b/test-mock/api/test-current.txt
@@ -6,13 +6,13 @@
   }
 
   @Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
-    method public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int);
-    method public void clearCrossProfileIntentFilters(int);
-    method public int getInstallReason(String, android.os.UserHandle);
-    method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
-    method public String[] getNamesForUids(int[]);
-    method @NonNull public String getServicesSystemSharedLibraryPackageName();
-    method @NonNull public String getSharedSystemSharedLibraryPackageName();
+    method @Deprecated public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int);
+    method @Deprecated public void clearCrossProfileIntentFilters(int);
+    method @Deprecated public int getInstallReason(String, android.os.UserHandle);
+    method @Deprecated public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
+    method @Deprecated public String[] getNamesForUids(int[]);
+    method @Deprecated @NonNull public String getServicesSystemSharedLibraryPackageName();
+    method @Deprecated @NonNull public String getSharedSystemSharedLibraryPackageName();
   }
 
 }
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/Input/Android.bp b/tests/Input/Android.bp
index f910b8b..cf2d5d6 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -9,6 +9,10 @@
 
 android_test {
     name: "InputTests",
+    defaults: [
+        // For ExtendedMockito dependencies.
+        "modules-utils-testable-device-config-defaults",
+    ],
     srcs: [
         "src/**/*.java",
         "src/**/*.kt",
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index b6477510..44de6a6 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -32,11 +32,16 @@
 import android.os.test.TestLooper
 import android.platform.test.annotations.Presubmit
 import android.provider.Settings
+import android.util.proto.ProtoOutputStream
 import android.view.InputDevice
 import android.view.inputmethod.InputMethodInfo
 import android.view.inputmethod.InputMethodSubtype
 import androidx.test.core.R
 import androidx.test.core.app.ApplicationProvider
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.internal.os.KeyboardConfiguredProto
+import com.android.internal.util.FrameworkStatsLog
+import com.android.modules.utils.testing.ExtendedMockitoRule
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotEquals
@@ -46,9 +51,9 @@
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+import org.mockito.ArgumentMatchers
 import org.mockito.Mock
 import org.mockito.Mockito
-import org.mockito.junit.MockitoJUnit
 import java.io.FileNotFoundException
 import java.io.FileOutputStream
 import java.io.IOException
@@ -95,16 +100,23 @@
         const val RECEIVER_NAME = "DummyReceiver"
         private const val ENGLISH_US_LAYOUT_NAME = "keyboard_layout_english_us"
         private const val ENGLISH_UK_LAYOUT_NAME = "keyboard_layout_english_uk"
+        private const val GERMAN_LAYOUT_NAME = "keyboard_layout_german"
         private const val VENDOR_SPECIFIC_LAYOUT_NAME = "keyboard_layout_vendorId:1,productId:1"
+        const val LAYOUT_TYPE_QWERTZ = 2
+        const val LAYOUT_TYPE_QWERTY = 1
+        const val LAYOUT_TYPE_DEFAULT = 0
     }
 
     private val ENGLISH_US_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_US_LAYOUT_NAME)
     private val ENGLISH_UK_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_UK_LAYOUT_NAME)
+    private val GERMAN_LAYOUT_DESCRIPTOR = createLayoutDescriptor(GERMAN_LAYOUT_NAME)
     private val VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR =
         createLayoutDescriptor(VENDOR_SPECIFIC_LAYOUT_NAME)
 
-    @get:Rule
-    val rule = MockitoJUnit.rule()!!
+    @JvmField
+    @Rule
+    val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
+            .mockStatic(FrameworkStatsLog::class.java).build()!!
 
     @Mock
     private lateinit var iInputManager: IInputManager
@@ -145,7 +157,9 @@
             override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
         })
         testLooper = TestLooper()
-        keyboardLayoutManager = KeyboardLayoutManager(context, native, dataStore, testLooper.looper)
+        keyboardLayoutManager = Mockito.spy(
+            KeyboardLayoutManager(context, native, dataStore, testLooper.looper)
+        )
         setupInputDevices()
         setupBroadcastReceiver()
         setupIme()
@@ -698,7 +712,7 @@
             assertCorrectLayout(
                 keyboardDevice,
                 createImeSubtypeForLanguageTag("de"),
-                createLayoutDescriptor("keyboard_layout_german")
+                GERMAN_LAYOUT_DESCRIPTOR
             )
             assertCorrectLayout(
                 keyboardDevice,
@@ -763,13 +777,13 @@
             assertCorrectLayout(
                 keyboardDevice,
                 createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"),
-                createLayoutDescriptor("keyboard_layout_german")
+                GERMAN_LAYOUT_DESCRIPTOR
             )
             // Wrong layout type should match with language if provided layout type not available
             assertCorrectLayout(
                 keyboardDevice,
                 createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"),
-                createLayoutDescriptor("keyboard_layout_german")
+                GERMAN_LAYOUT_DESCRIPTOR
             )
             assertCorrectLayout(
                 keyboardDevice,
@@ -827,6 +841,100 @@
         }
     }
 
+    @Test
+    fun testConfigurationLogged_onInputDeviceAdded_VirtualKeyboardBasedSelection() {
+        val imeInfos = listOf(
+                KeyboardLayoutManager.ImeInfo(0, imeInfo,
+                        createImeSubtypeForLanguageTagAndLayoutType("de-Latn", "qwertz")))
+        Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
+        NewSettingsApiFlag(true).use {
+            keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
+            ExtendedMockito.verify {
+                FrameworkStatsLog.write(
+                        ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+                        ArgumentMatchers.anyBoolean(),
+                        ArgumentMatchers.eq(keyboardDevice.vendorId),
+                        ArgumentMatchers.eq(keyboardDevice.productId),
+                        ArgumentMatchers.eq(createByteArray(
+                                KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+                                LAYOUT_TYPE_DEFAULT,
+                                GERMAN_LAYOUT_NAME,
+                                KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+                                "de-Latn",
+                                LAYOUT_TYPE_QWERTZ))
+                )
+            }
+        }
+    }
+
+    @Test
+    fun testConfigurationLogged_onInputDeviceAdded_DeviceBasedSelection() {
+        val imeInfos = listOf(
+                KeyboardLayoutManager.ImeInfo(0, imeInfo,
+                        createImeSubtypeForLanguageTagAndLayoutType("de-Latn", "qwertz")))
+        Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
+        NewSettingsApiFlag(true).use {
+            keyboardLayoutManager.onInputDeviceAdded(englishQwertyKeyboardDevice.id)
+            ExtendedMockito.verify {
+                FrameworkStatsLog.write(
+                        ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+                        ArgumentMatchers.anyBoolean(),
+                        ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId),
+                        ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId),
+                        ArgumentMatchers.eq(createByteArray(
+                                "en",
+                                LAYOUT_TYPE_QWERTY,
+                                ENGLISH_US_LAYOUT_NAME,
+                                KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+                                "de-Latn",
+                                LAYOUT_TYPE_QWERTZ))
+                )
+            }
+        }
+    }
+
+    @Test
+    fun testConfigurationLogged_onInputDeviceAdded_DefaultSelection() {
+        val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype()))
+        Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
+        NewSettingsApiFlag(true).use {
+            keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
+            ExtendedMockito.verify {
+                FrameworkStatsLog.write(
+                        ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+                        ArgumentMatchers.anyBoolean(),
+                        ArgumentMatchers.eq(keyboardDevice.vendorId),
+                        ArgumentMatchers.eq(keyboardDevice.productId),
+                        ArgumentMatchers.eq(createByteArray(
+                                KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+                                LAYOUT_TYPE_DEFAULT,
+                                "Default",
+                                KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT,
+                                KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+                                LAYOUT_TYPE_DEFAULT))
+                )
+            }
+        }
+    }
+
+    @Test
+    fun testConfigurationNotLogged_onInputDeviceChanged() {
+        val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype()))
+        Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
+        NewSettingsApiFlag(true).use {
+            keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
+            ExtendedMockito.verify({
+                FrameworkStatsLog.write(
+                        ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+                        ArgumentMatchers.anyBoolean(),
+                        ArgumentMatchers.anyInt(),
+                        ArgumentMatchers.anyInt(),
+                        ArgumentMatchers.any(ByteArray::class.java)
+                )
+            }, Mockito.times(0))
+        }
+    }
+
     private fun assertCorrectLayout(
         device: InputDevice,
         imeSubtype: InputMethodSubtype,
@@ -842,18 +950,60 @@
     }
 
     private fun createImeSubtype(): InputMethodSubtype =
-        InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++).build()
+            createImeSubtypeForLanguageTagAndLayoutType(null, null)
 
     private fun createImeSubtypeForLanguageTag(languageTag: String): InputMethodSubtype =
-        InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++)
-            .setLanguageTag(languageTag).build()
+            createImeSubtypeForLanguageTagAndLayoutType(languageTag, null)
 
     private fun createImeSubtypeForLanguageTagAndLayoutType(
-        languageTag: String,
-        layoutType: String
-    ): InputMethodSubtype =
-        InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++)
-            .setPhysicalKeyboardHint(ULocale.forLanguageTag(languageTag), layoutType).build()
+            languageTag: String?,
+            layoutType: String?
+    ): InputMethodSubtype {
+        val builder = InputMethodSubtype.InputMethodSubtypeBuilder()
+                .setSubtypeId(nextImeSubtypeId++)
+                .setIsAuxiliary(false)
+                .setSubtypeMode("keyboard")
+        if (languageTag != null && layoutType != null) {
+            builder.setPhysicalKeyboardHint(ULocale.forLanguageTag(languageTag), layoutType)
+        } else if (languageTag != null) {
+            builder.setLanguageTag(languageTag)
+        }
+        return builder.build()
+    }
+
+    private fun createByteArray(
+            expectedLanguageTag: String, expectedLayoutType: Int, expectedLayoutName: String,
+            expectedCriteria: Int, expectedImeLanguageTag: String, expectedImeLayoutType: Int): ByteArray {
+        val proto = ProtoOutputStream()
+        val keyboardLayoutConfigToken = proto.start(
+                KeyboardConfiguredProto.RepeatedKeyboardLayoutConfig.KEYBOARD_LAYOUT_CONFIG)
+        proto.write(
+                KeyboardConfiguredProto.KeyboardLayoutConfig.KEYBOARD_LANGUAGE_TAG,
+                expectedLanguageTag
+        )
+        proto.write(
+                KeyboardConfiguredProto.KeyboardLayoutConfig.KEYBOARD_LAYOUT_TYPE,
+                expectedLayoutType
+        )
+        proto.write(
+                KeyboardConfiguredProto.KeyboardLayoutConfig.KEYBOARD_LAYOUT_NAME,
+                expectedLayoutName
+        )
+        proto.write(
+                KeyboardConfiguredProto.KeyboardLayoutConfig.LAYOUT_SELECTION_CRITERIA,
+                expectedCriteria
+        )
+        proto.write(
+                KeyboardConfiguredProto.KeyboardLayoutConfig.IME_LANGUAGE_TAG,
+                expectedImeLanguageTag
+        )
+        proto.write(
+                KeyboardConfiguredProto.KeyboardLayoutConfig.IME_LAYOUT_TYPE,
+                expectedImeLayoutType
+        )
+        proto.end(keyboardLayoutConfigToken);
+        return proto.bytes
+    }
 
     private fun hasLayout(layoutList: Array<KeyboardLayout>, layoutDesc: String): Boolean {
         for (kl in layoutList) {
diff --git a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
index b39c932..33ff09b 100644
--- a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
@@ -113,7 +113,7 @@
         )
         val event = builder.addLayoutSelection(
             createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"),
-            KeyboardLayout(null, "English(US)(Qwerty)", null, 0, null, 0, 0, 0),
+            "English(US)(Qwerty)",
             KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
         ).addLayoutSelection(
             createImeSubtype(2, ULocale.forLanguageTag("en-US"), "azerty"),
@@ -121,7 +121,7 @@
             KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER
         ).addLayoutSelection(
             createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"),
-            KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
+            "German",
             KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
         ).setIsFirstTimeConfiguration(true).build()
 
@@ -184,7 +184,7 @@
         )
         val event = builder.addLayoutSelection(
             createImeSubtype(4, null, "qwerty"), // Default language tag
-            KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
+            "German",
             KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
         ).build()
 
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()
     }
diff --git a/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java b/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java
index 6a6ab00..a43e1b0 100644
--- a/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java
+++ b/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java
@@ -21,15 +21,39 @@
 
 public class TestableFlagResolver implements SystemUiSystemPropertiesFlags.FlagResolver {
     private Map<String, Boolean> mOverrides = new HashMap<>();
+    private Map<String, Integer> mOverridesInt = new HashMap<>();
+    private Map<String, String> mOverridesString = new HashMap<>();
 
     @Override
     public boolean isEnabled(SystemUiSystemPropertiesFlags.Flag flag) {
         return mOverrides.getOrDefault(flag.mSysPropKey, flag.mDefaultValue);
     }
 
+    @Override
+    public int getIntValue(SystemUiSystemPropertiesFlags.Flag flag) {
+        return mOverridesInt.getOrDefault(flag.mSysPropKey, flag.mDefaultIntValue);
+    }
+
+    @Override
+    public String getStringValue(SystemUiSystemPropertiesFlags.Flag flag) {
+        return mOverridesString.getOrDefault(flag.mSysPropKey, flag.mDefaultStringValue);
+    }
+
     public TestableFlagResolver setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag,
             boolean isEnabled) {
         mOverrides.put(flag.mSysPropKey, isEnabled);
         return this;
     }
+
+    public TestableFlagResolver setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag,
+        int value) {
+        mOverridesInt.put(flag.mSysPropKey, value);
+        return this;
+    }
+
+    public TestableFlagResolver setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag,
+        String value) {
+        mOverridesString.put(flag.mSysPropKey, value);
+        return this;
+    }
 }
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 7323b0f..977b276 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -220,6 +220,7 @@
     name: "aapt2-protos",
     tools: [":soong_zip"],
     srcs: [
+        "ApkInfo.proto",
         "Configuration.proto",
         "ResourcesInternal.proto",
         "ResourceMetadata.proto",
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
index 87da09a..8c644cf 100644
--- a/tools/aapt2/java/AnnotationProcessor.cpp
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -49,16 +49,19 @@
     kDeprecated = 0x01,
     kSystemApi = 0x02,
     kTestApi = 0x04,
+    kFlaggedApi = 0x08,
   };
 
   StringPiece doc_str;
   uint32_t bit_mask;
   StringPiece annotation;
+  bool preserve_params;
 };
 
-static std::array<AnnotationRule, 2> sAnnotationRules = {{
-    {"@SystemApi", AnnotationRule::kSystemApi, "@android.annotation.SystemApi"},
-    {"@TestApi", AnnotationRule::kTestApi, "@android.annotation.TestApi"},
+static std::array<AnnotationRule, 3> sAnnotationRules = {{
+    {"@SystemApi", AnnotationRule::kSystemApi, "@android.annotation.SystemApi", true},
+    {"@TestApi", AnnotationRule::kTestApi, "@android.annotation.TestApi", false},
+    {"@FlaggedApi", AnnotationRule::kFlaggedApi, "@android.annotation.FlaggedApi", true},
 }};
 
 void AnnotationProcessor::AppendCommentLine(std::string comment) {
@@ -73,12 +76,11 @@
     std::string::size_type idx = comment.find(rule.doc_str.data());
     if (idx != std::string::npos) {
       // Captures all parameters associated with the specified annotation rule
-      // by matching the first pair of parantheses after the rule.
-      std::regex re(std::string(rule.doc_str) += "\\s*\\((.+)\\)");
+      // by matching the first pair of parentheses after the rule.
+      std::regex re(std::string(rule.doc_str).append(R"(\s*\((.+)\))"));
       std::smatch match_result;
       const bool is_match = std::regex_search(comment, match_result, re);
-      // We currently only capture and preserve parameters for SystemApi.
-      if (is_match && rule.bit_mask == AnnotationRule::kSystemApi) {
+      if (is_match && rule.preserve_params) {
         annotation_parameter_map_[rule.bit_mask] = match_result[1].str();
         comment.erase(comment.begin() + match_result.position(),
                       comment.begin() + match_result.position() + match_result.length());
diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp
index 6bc8902..e98e96b 100644
--- a/tools/aapt2/java/AnnotationProcessor_test.cpp
+++ b/tools/aapt2/java/AnnotationProcessor_test.cpp
@@ -76,6 +76,36 @@
   EXPECT_THAT(annotations, HasSubstr("This is a system API"));
 }
 
+TEST(AnnotationProcessorTest, EmitsFlaggedApiAnnotationAndRemovesFromComment) {
+  AnnotationProcessor processor;
+  processor.AppendComment("@FlaggedApi This is a flagged API");
+
+  std::string annotations;
+  StringOutputStream out(&annotations);
+  Printer printer(&out);
+  processor.Print(&printer);
+  out.Flush();
+
+  EXPECT_THAT(annotations, HasSubstr("@android.annotation.FlaggedApi"));
+  EXPECT_THAT(annotations, Not(HasSubstr("@FlaggedApi")));
+  EXPECT_THAT(annotations, HasSubstr("This is a flagged API"));
+}
+
+TEST(AnnotationProcessorTest, EmitsFlaggedApiAnnotationParamsAndRemovesFromComment) {
+  AnnotationProcessor processor;
+  processor.AppendComment("@FlaggedApi (\"android.flags.my_flag\") This is a flagged API");
+
+  std::string annotations;
+  StringOutputStream out(&annotations);
+  Printer printer(&out);
+  processor.Print(&printer);
+  out.Flush();
+
+  EXPECT_THAT(annotations, HasSubstr("@android.annotation.FlaggedApi(\"android.flags.my_flag\")"));
+  EXPECT_THAT(annotations, Not(HasSubstr("@FlaggedApi")));
+  EXPECT_THAT(annotations, HasSubstr("This is a flagged API"));
+}
+
 TEST(AnnotationProcessorTest, EmitsTestApiAnnotationAndRemovesFromComment) {
   AnnotationProcessor processor;
   processor.AppendComment("@TestApi This is a test API");
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index a617876..0599c1d 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -30,11 +30,6 @@
     ],
     libs: [
         "junit",
-        "ow2-asm",
-        "ow2-asm-analysis",
-        "ow2-asm-commons",
-        "ow2-asm-tree",
-        "ow2-asm-util",
     ],
     static_libs: [
         "guava",
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java
index 4c37579..7ada961 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java
@@ -17,8 +17,6 @@
 
 import static java.lang.annotation.ElementType.TYPE;
 
-import org.objectweb.asm.Type;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
@@ -29,6 +27,6 @@
 @Target({TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 public @interface HostStubGenProcessedKeepClass {
-    String CLASS_INTERNAL_NAME = Type.getInternalName(HostStubGenProcessedKeepClass.class);
+    String CLASS_INTERNAL_NAME = HostTestUtils.getInternalName(HostStubGenProcessedKeepClass.class);
     String CLASS_DESCRIPTOR = "L" + CLASS_INTERNAL_NAME + ";";
 }
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java
index 34e0030..e598da0a 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java
@@ -17,8 +17,6 @@
 
 import static java.lang.annotation.ElementType.TYPE;
 
-import org.objectweb.asm.Type;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
@@ -29,6 +27,6 @@
 @Target({TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 public @interface HostStubGenProcessedStubClass {
-    String CLASS_INTERNAL_NAME = Type.getInternalName(HostStubGenProcessedStubClass.class);
+    String CLASS_INTERNAL_NAME = HostTestUtils.getInternalName(HostStubGenProcessedStubClass.class);
     String CLASS_DESCRIPTOR = "L" + CLASS_INTERNAL_NAME + ";";
 }
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
index d176b5e..9f83597 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
@@ -15,8 +15,6 @@
  */
 package com.android.hoststubgen.hosthelper;
 
-import org.objectweb.asm.Type;
-
 import java.io.PrintStream;
 import java.lang.StackWalker.Option;
 import java.lang.reflect.Method;
@@ -32,7 +30,15 @@
     private HostTestUtils() {
     }
 
-    public static final String CLASS_INTERNAL_NAME = Type.getInternalName(HostTestUtils.class);
+    /**
+     * Same as ASM's Type.getInternalName(). Copied here, to avoid having a reference to ASM
+     * in this JAR.
+     */
+    public static String getInternalName(final Class<?> clazz) {
+        return clazz.getName().replace('.', '/');
+    }
+
+    public static final String CLASS_INTERNAL_NAME = getInternalName(HostTestUtils.class);
 
     /** If true, we won't print method call log. */
     private static final boolean SKIP_METHOD_LOG = "1".equals(System.getenv(
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt
index 33010ba..678e6ea 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt
@@ -46,8 +46,11 @@
         }
         methodPolicy = policy
 
-        // TODO: Need to think about the realistic default behavior.
-        classPolicy = if (policy != FilterPolicy.Throw) policy else FilterPolicy.Remove
+        // If the default policy is "throw", we convert it to "keep" for classes and fields.
+        classPolicy = when (policy) {
+            FilterPolicy.Throw -> FilterPolicy.Keep
+            else -> policy
+        }
         fieldPolicy = classPolicy
     }
 
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
index 9c372ff..c6334c4 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
@@ -17,6 +17,8 @@
 
 import com.android.hoststubgen.HostStubGenErrors
 import com.android.hoststubgen.HostStubGenInternalException
+import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
+import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
 import com.android.hoststubgen.asm.isAnonymousInnerClass
 import com.android.hoststubgen.log
 import com.android.hoststubgen.asm.ClassNodes
@@ -81,6 +83,16 @@
             }
         }
 
+        // If we throw from the static initializer, the class would be useless, so we convert it
+        // "keep" instead.
+        if (methodName == CLASS_INITIALIZER_NAME && descriptor == CLASS_INITIALIZER_DESC &&
+            fallback.policy == FilterPolicy.Throw) {
+            // TODO Maybe show a warning?? But that'd be too noisy with --default-throw.
+            return FilterPolicy.Keep.withReason(
+                "'throw' on static initializer is handled as 'keep'" +
+                        " [original throw reason: ${fallback.reason}]")
+        }
+
         return fallback
     }
 }
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
index 57b6689..ce72a8e 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
@@ -79,7 +79,7 @@
             // StaticInitMerger will merge it with the existing one, if any.
             visitMethod(
                 Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC,
-                "<clinit>",
+                CLASS_INITIALIZER_NAME,
                 "()V",
                 null,
                 null
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
index 43ceec4..0761edc 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -372,7 +372,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 2, attributes: 3
+  interfaces: 0, fields: 1, methods: 1, attributes: 3
   public static boolean sInitialized;
     descriptor: Z
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -387,17 +387,6 @@
          x: ldc           #x                 // String Stub!
          x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
          x: athrow
-
-  static {};
-    descriptor: ()V
-    flags: (0x0008) ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
 }
 SourceFile: "TinyFrameworkClassWithInitializer.java"
 RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
index 43ceec4..0761edc 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
@@ -372,7 +372,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 2, attributes: 3
+  interfaces: 0, fields: 1, methods: 1, attributes: 3
   public static boolean sInitialized;
     descriptor: Z
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -387,17 +387,6 @@
          x: ldc           #x                 // String Stub!
          x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
          x: athrow
-
-  static {};
-    descriptor: ()V
-    flags: (0x0008) ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
 }
 SourceFile: "TinyFrameworkClassWithInitializer.java"
 RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
index 079d2a8..8fcd2fb 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
@@ -15,3 +15,8 @@
 
 # Class load hook
 class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy	~com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+
+
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializer stubclass
+  # Testing 'throw' on a static initializer. This should be handled as 'keep'.
+  method <clinit>	()V	 throw
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
index fd48646..6bf074b 100755
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
@@ -18,8 +18,6 @@
 
 # This scripts run the "tiny-framework" test, but does most stuff from the command line, using
 # the native java and javac commands.
-# This is useful to
-
 
 debug=0
 while getopts "d" opt; do
@@ -57,7 +55,7 @@
 
 test_compile_classpaths=(
   $SOONG_INT/external/junit/junit/android_common/combined/junit.jar
-  $SOONG_INT/prebuilts/tools/common/m2/truth-prebuilt/android_common/combined/truth-prebuilt.jar
+  $ANDROID_HOST_OUT/framework/truth-prebuilt.jar
 )
 
 test_runtime_classpaths=(
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java
index 53cfdf6..01a690b 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java
@@ -18,10 +18,14 @@
 import android.hosttest.annotation.HostSideTestClassLoadHook;
 import android.hosttest.annotation.HostSideTestWholeClassStub;
 
+
+// Note, policy-override-tiny-framework.txt hss an override on this class.
 @HostSideTestClassLoadHook(
         "com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded")
 @HostSideTestWholeClassStub
 public class TinyFrameworkClassWithInitializer {
+    // Note, this method has a 'throw' in the policy file, which is handled as a 'keep' (because
+    // it's a static initializer), so this won't show up in the stub jar.
     static {
         sInitialized = true;
     }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index 246d065..cd604ff 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -23,6 +23,10 @@
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 
+import java.io.FileDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
 public class TinyFrameworkClassTest {
     @Rule
     public ExpectedException thrown = ExpectedException.none();
@@ -140,4 +144,42 @@
         assertThat(TinyFrameworkClassLoadHook.sLoadedClasses)
                 .doesNotContain(TinyFrameworkNestedClasses.class);
     }
+
+    /**
+     * Test to try accessing JDK private fields using reflections + setAccessible(true),
+     * which is now disallowed due to Java Modules, unless you run the javacommand with.
+     *   --add-opens=java.base/java.io=ALL-UNNAMED
+     *
+     * You can try it from the command line, like:
+     * $ JAVA_OPTS="--add-opens=java.base/java.io=ALL-UNNAMED" ./run-test-manually.sh
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testFileDescriptor() throws Exception {
+        var fd = FileDescriptor.out;
+
+        // Get the FD value directly from the private field.
+        // This is now prohibited due to Java Modules.
+        // It throws:
+        // java.lang.reflect.InaccessibleObjectException: Unable to make field private int java.io.FileDescriptor.fd accessible: module java.base does not "opens java.io" to unnamed module @3bb50eaa
+
+        thrown.expect(java.lang.reflect.InaccessibleObjectException.class);
+
+        // Access the private field.
+        final Field f = FileDescriptor.class.getDeclaredField("fd");
+        final Method m = FileDescriptor.class.getDeclaredMethod("set", int.class);
+        f.setAccessible(true);
+        m.setAccessible(true);
+
+        assertThat(f.get(fd)).isEqualTo(1);
+
+        // Set
+        f.set(fd, 2);
+        assertThat(f.get(fd)).isEqualTo(2);
+
+        // Call the package private method, set(int).
+        m.invoke(fd, 0);
+        assertThat(f.get(fd)).isEqualTo(0);
+    }
 }
diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh
index 7600942..8bc88de 100755
--- a/tools/hoststubgen/scripts/run-all-tests.sh
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -33,6 +33,7 @@
 run ./hoststubgen/test-tiny-framework/diff-and-update-golden.sh
 
 run ./hoststubgen/test-framework/run-test-without-atest.sh
+
 run ./hoststubgen/test-tiny-framework/run-test-manually.sh
 run atest tiny-framework-dump-test
 run ./scripts/build-framework-hostside-jars-and-extract.sh
diff --git a/tools/lint/global/integration_tests/Android.bp b/tools/lint/global/integration_tests/Android.bp
index ca96559..40281d2 100644
--- a/tools/lint/global/integration_tests/Android.bp
+++ b/tools/lint/global/integration_tests/Android.bp
@@ -12,25 +12,58 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-java_library {
-    name: "AndroidGlobalLintTestNoAidl",
-    srcs: ["TestNoAidl/**/*.java"],
+// Integration tests for @EnforcePermission linters.
+// Each test defines its own java_library. The XML lint report from this
+// java_library is wrapped under a Python library with a unique pkg_path (this
+// is to avoid a name conflict for the report file). All the tests are
+// referenced and executed by AndroidGlobalLintCheckerIntegrationTest.
+
+java_defaults {
+    name: "AndroidGlobalLintIntegrationTestDefault",
     libs: [
         "framework-annotations-lib",
     ],
     lint: {
-        // It is expected that lint returns an error when processing this
+        // It is expected that lint returns an error when processing the
         // library. Silence it here, the lint output is verified in tests.py.
         suppress_exit_code: true,
     },
 }
 
+java_library {
+    name: "AndroidGlobalLintTestNoAidl",
+    srcs: ["TestNoAidl/**/*.java"],
+    defaults: ["AndroidGlobalLintIntegrationTestDefault"],
+}
+
+python_library_host {
+    name: "AndroidGlobalLintTestNoAidl_py",
+    data: [":AndroidGlobalLintTestNoAidl{.lint}"],
+    pkg_path: "no_aidl",
+}
+
+java_library {
+    name: "AndroidGlobalLintTestMissingAnnotation",
+    srcs: [
+        "TestMissingAnnotation/**/*.java",
+        "TestMissingAnnotation/**/*.aidl",
+    ],
+    defaults: ["AndroidGlobalLintIntegrationTestDefault"],
+}
+
+python_library_host {
+    name: "AndroidGlobalLintTestMissingAnnotation_py",
+    data: [":AndroidGlobalLintTestMissingAnnotation{.lint}"],
+    pkg_path: "missing_annotation",
+}
+
 python_test_host {
     name: "AndroidGlobalLintCheckerIntegrationTest",
     srcs: ["tests.py"],
     main: "tests.py",
-    data: [
-        ":AndroidGlobalLintTestNoAidl{.lint}",
+    libs: [
+        "AndroidGlobalLintTestNoAidl_py",
+        "AndroidGlobalLintTestMissingAnnotation_py",
     ],
     version: {
         py3: {
diff --git a/tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java b/tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java
new file mode 100644
index 0000000..9e4854c
--- /dev/null
+++ b/tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java
@@ -0,0 +1,28 @@
+/*
+ * 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.google.android.lint.integration_tests;
+
+/**
+ * A class that implements an AIDL interface, but is missing the @EnforcePermission annotation.
+ */
+class TestMissingAnnotation extends IFoo.Stub {
+
+    @Override
+    public void Method() {
+    }
+
+}
diff --git a/tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl b/tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl
new file mode 100644
index 0000000..95ec2c2
--- /dev/null
+++ b/tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl
@@ -0,0 +1,7 @@
+package com.google.android.lint.integration_tests;
+
+interface IFoo {
+
+    @EnforcePermission("INTERNET")
+    void Method();
+}
diff --git a/tools/lint/global/integration_tests/tests.py b/tools/lint/global/integration_tests/tests.py
index fc3eeb4..cdb16b8 100644
--- a/tools/lint/global/integration_tests/tests.py
+++ b/tools/lint/global/integration_tests/tests.py
@@ -19,16 +19,28 @@
 class TestLinterReports(unittest.TestCase):
     """Integration tests for the linters used by @EnforcePermission."""
 
-    def test_no_aidl(self):
-        report = pkgutil.get_data("lint", "lint-report.xml").decode()
+    def _read_report(self, pkg_path):
+        report = pkgutil.get_data(pkg_path, "lint/lint-report.xml").decode()
         issues = xml.etree.ElementTree.fromstring(report)
         self.assertEqual(issues.tag, "issues")
+        return issues
+
+    def test_no_aidl(self):
+        issues = self._read_report("no_aidl")
         self.assertEqual(len(issues), 1)
 
         issue = issues[0]
         self.assertEqual(issue.attrib["id"], "MisusingEnforcePermissionAnnotation")
         self.assertEqual(issue.attrib["severity"], "Error")
 
+    def test_missing_annotation(self):
+        issues = self._read_report("missing_annotation")
+        self.assertEqual(len(issues), 1)
+
+        issue = issues[0]
+        self.assertEqual(issue.attrib["id"], "MissingEnforcePermissionAnnotation")
+        self.assertEqual(issue.attrib["severity"], "Error")
+
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)