Merge "Move FakeLogBuffer to test utils" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index bfb0754..36430b6 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -33,10 +33,13 @@
     ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
     ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
     ":android.widget.flags-aconfig-java{.generated_srcjars}",
+    ":com.android.media.audio.flags-aconfig-java{.generated_srcjars}",
     ":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}",
@@ -315,6 +318,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Media Audio
+java_aconfig_library {
+    name: "com.android.media.audio.flags-aconfig-java",
+    aconfig_declarations: "aconfig_audio_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Permissions
 aconfig_declarations {
     name: "android.permission.flags-aconfig",
@@ -333,6 +343,19 @@
 
 }
 
+// SQLite
+aconfig_declarations {
+    name: "android.database.sqlite-aconfig",
+    package: "android.database.sqlite",
+    srcs: ["core/java/android/database/sqlite/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.database.sqlite-aconfig-java",
+    aconfig_declarations: "android.database.sqlite-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Biometrics
 aconfig_declarations {
     name: "android.hardware.biometrics.flags-aconfig",
@@ -353,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",
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/api/javadoc-lint-baseline b/api/javadoc-lint-baseline
index c28480e..d9e72b8 100644
--- a/api/javadoc-lint-baseline
+++ b/api/javadoc-lint-baseline
@@ -53,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]
@@ -83,17 +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/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/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]
@@ -119,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]
@@ -177,26 +151,6 @@
 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/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/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/PixelCopy.java:468: lint: Unresolved link/see tag "android.view.PixelCopy.CopyResultStatus CopyResultStatus" in android.view.PixelCopy.Result [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/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101]
@@ -218,8 +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/os/BatteryStatsManager.java:260: lint: Invalid tag: @Deprecated [131]
-android/os/BatteryStatsManager.java:275: lint: Invalid tag: @Deprecated [131]
-
-java/lang/ClassLoader.java:853: lint: Unknown tag: @systemProperty [103]
diff --git a/core/api/current.txt b/core/api/current.txt
index 20424be..dfe023a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android {
 
   public final class Manifest {
@@ -5741,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);
@@ -13653,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);
   }
 
 }
@@ -14352,14 +14352,14 @@
   public final class SQLiteDatabase extends android.database.sqlite.SQLiteClosable {
     method public void beginTransaction();
     method public void beginTransactionNonExclusive();
-    method public void beginTransactionReadOnly();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionReadOnly();
     method public void beginTransactionWithListener(@Nullable android.database.sqlite.SQLiteTransactionListener);
     method public void beginTransactionWithListenerNonExclusive(@Nullable android.database.sqlite.SQLiteTransactionListener);
-    method public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
     method public android.database.sqlite.SQLiteStatement compileStatement(String) throws android.database.SQLException;
     method @NonNull public static android.database.sqlite.SQLiteDatabase create(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);
     method @NonNull public static android.database.sqlite.SQLiteDatabase createInMemory(@NonNull android.database.sqlite.SQLiteDatabase.OpenParams);
-    method @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
     method public int delete(@NonNull String, @Nullable String, @Nullable String[]);
     method public static boolean deleteDatabase(@NonNull java.io.File);
     method public void disableWriteAheadLogging();
@@ -14370,13 +14370,13 @@
     method public void execSQL(@NonNull String, @NonNull Object[]) throws android.database.SQLException;
     method public static String findEditTable(String);
     method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs();
-    method public long getLastChangedRowCount();
-    method public long getLastInsertRowId();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastChangedRowCount();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastInsertRowId();
     method public long getMaximumSize();
     method public long getPageSize();
     method public String getPath();
     method @Deprecated public java.util.Map<java.lang.String,java.lang.String> getSyncedTables();
-    method public long getTotalChangedRowCount();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getTotalChangedRowCount();
     method public int getVersion();
     method public boolean inTransaction();
     method public long insert(@NonNull String, @Nullable String, @Nullable android.content.ContentValues);
@@ -14598,7 +14598,7 @@
     method public int update(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues, @Nullable String, @Nullable String[]);
   }
 
-  public final class SQLiteRawStatement implements java.io.Closeable {
+  @FlaggedApi("android.database.sqlite.sqlite_apis_15") public final class SQLiteRawStatement implements java.io.Closeable {
     method public void bindBlob(int, @NonNull byte[]) throws android.database.sqlite.SQLiteException;
     method public void bindBlob(int, @NonNull byte[], int, int) throws android.database.sqlite.SQLiteException;
     method public void bindDouble(int, double) throws android.database.sqlite.SQLiteException;
@@ -15671,7 +15671,7 @@
 
   public final class Gainmap implements android.os.Parcelable {
     ctor public Gainmap(@NonNull android.graphics.Bitmap);
-    ctor public Gainmap(@NonNull android.graphics.Gainmap, @NonNull android.graphics.Bitmap);
+    ctor @FlaggedApi("com.android.graphics.hwui.flags.gainmap_constructor_with_metadata") public Gainmap(@NonNull android.graphics.Gainmap, @NonNull android.graphics.Bitmap);
     method public int describeContents();
     method @NonNull public float getDisplayRatioForFullHdr();
     method @NonNull public float[] getEpsilonHdr();
@@ -16309,7 +16309,7 @@
     method public void arcTo(float, float, float, float, float, float, boolean);
     method public void close();
     method @Deprecated public void computeBounds(@NonNull android.graphics.RectF, boolean);
-    method public void computeBounds(@NonNull android.graphics.RectF);
+    method @FlaggedApi("com.android.graphics.flags.exact_compute_bounds") public void computeBounds(@NonNull android.graphics.RectF);
     method public void conicTo(float, float, float, float, float);
     method public void cubicTo(float, float, float, float, float, float);
     method @NonNull public android.graphics.Path.FillType getFillType();
@@ -20405,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);
@@ -24232,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;
@@ -27096,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();
@@ -33094,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();
@@ -33656,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[]);
@@ -43531,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;
   }
 
@@ -43547,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;
   }
 
@@ -43565,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;
   }
 
@@ -43578,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;
   }
 
@@ -43592,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;
   }
 
@@ -43608,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;
   }
 
@@ -43692,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();
@@ -43702,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();
@@ -43730,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();
@@ -43748,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;
   }
@@ -46555,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 {
@@ -47006,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 {
@@ -47135,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);
   }
 
@@ -48775,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();
@@ -48807,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();
@@ -57546,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 {
@@ -57624,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);
@@ -58270,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);
   }
 
@@ -58323,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);
@@ -58545,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);
@@ -59093,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);
@@ -59547,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);
@@ -60193,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);
@@ -60347,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 500a12c..b5d3ed7 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android {
 
   public static final class Manifest.permission {
diff --git a/core/api/module-lib-removed.txt b/core/api/module-lib-removed.txt
index 14191eb..d802177 100644
--- a/core/api/module-lib-removed.txt
+++ b/core/api/module-lib-removed.txt
@@ -1,3 +1 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/core/api/removed.txt b/core/api/removed.txt
index e2b4e4d..5a4be65 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android.app {
 
   public class Notification implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7dcc7b2..9ecce14 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android {
 
   public static final class Manifest.permission {
@@ -299,6 +297,7 @@
     field public static final String RECEIVE_DATA_ACTIVITY_CHANGE = "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE";
     field public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY";
     field public static final String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST";
+    field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA = "android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA";
     field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOX_TRIGGER_AUDIO = "android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO";
     field public static final String RECEIVE_WIFI_CREDENTIAL_CHANGE = "android.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE";
     field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
@@ -313,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";
@@ -4060,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;
   }
 
@@ -5960,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();
@@ -5982,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);
@@ -5989,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);
@@ -6716,7 +6720,7 @@
     method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
     method public boolean setUserIdDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
     method public String toLogFriendlyString();
-    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int updateMixingRules(@NonNull java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>);
+    method @FlaggedApi("com.android.media.audio.flags.audio_policy_update_mixing_rules_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int updateMixingRules(@NonNull java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>);
     field public static final int FOCUS_POLICY_DUCKING_DEFAULT = 0; // 0x0
     field public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; // 0x0
     field public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1; // 0x1
@@ -6799,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);
   }
 
@@ -6857,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);
@@ -7570,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();
@@ -8964,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 {
@@ -8978,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 {
@@ -8996,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 {
@@ -9007,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
@@ -9045,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
@@ -9057,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
@@ -9067,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
@@ -9083,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 {
@@ -9097,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
@@ -11502,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();
@@ -11580,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);
@@ -11618,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);
   }
@@ -11643,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);
@@ -11693,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);
@@ -11731,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);
@@ -11744,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);
   }
 
@@ -12416,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);
   }
 
@@ -13041,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;
   }
 
@@ -13350,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
@@ -13634,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
@@ -13643,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();
@@ -13745,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();
@@ -14860,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";
   }
@@ -15051,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";
@@ -15553,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();
diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt
index 1fa2718..aa17df3 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android.app {
 
   public class AppOpsManager {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index eeddeb2..0e857a9 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android {
 
   public static final class Manifest.permission {
@@ -3946,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
@@ -4007,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;
   }
 
@@ -4023,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();
@@ -4036,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;
   }
 
@@ -4058,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-removed.txt b/core/api/test-removed.txt
index 14191eb..d802177 100644
--- a/core/api/test-removed.txt
+++ b/core/api/test-removed.txt
@@ -1,3 +1 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
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/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 17637df..ecbc9b1 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1487,13 +1487,13 @@
             AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
 
     /**
-     * Allows the assistant app to get the training data from the trusted process to improve the
-     * hotword training model.
+     * Allows the privileged assistant app to receive the training data from the sandboxed hotword
+     * detection service.
      *
      * @hide
      */
-    public static final int OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA =
-            AppProtoEnums.APP_OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA;
+    public static final int OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA =
+            AppProtoEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;
 
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1641,7 +1641,7 @@
             OPSTR_CAMERA_SANDBOXED,
             OPSTR_RECORD_AUDIO_SANDBOXED,
             OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
-            OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA
+            OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA
     })
     public @interface AppOpString {}
 
@@ -2262,13 +2262,13 @@
             "android:receive_sandbox_trigger_audio";
 
     /**
-     * Allows the assistant app to get the training data from the trusted process to improve
-     * the hotword training model.
+     * Allows the privileged assistant app to receive training data from the sandboxed hotword
+     * detection service.
      *
      * @hide
      */
-    public static final String OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA =
-            "android:receive_trusted_process_training_data";
+    public static final String OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA =
+            "android:RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA";
 
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
@@ -2381,7 +2381,8 @@
             OP_FOREGROUND_SERVICE_SPECIAL_USE,
             OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
             OP_USE_FULL_SCREEN_INTENT,
-            OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
+            OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
+            OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA
     };
 
     static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2814,9 +2815,11 @@
                 "RECEIVE_SANDBOX_TRIGGER_AUDIO")
                 .setPermission(Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO)
                 .setDefaultMode(AppOpsManager.MODE_DEFAULT).build(),
-        new AppOpInfo.Builder(OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA,
-                OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA,
-                "RECEIVE_TRUSTED_PROCESS_TRAINING_DATA").build()
+        new AppOpInfo.Builder(OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
+                OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
+                "RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA")
+                .setPermission(Manifest.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA)
+                .setDefaultMode(AppOpsManager.MODE_DEFAULT).build()
     };
 
     // The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/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/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/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/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 5f4c05f..1eb2cd1 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4180,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 =
@@ -4194,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 =
@@ -4222,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.
      *
@@ -4242,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/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 45338bb..4d5d056 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4605,6 +4605,16 @@
     public static final String FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS =
             "android.software.wallet_location_based_suggestions";
 
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+     * the rotary encoder hardware to support rotating bezel on watch.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_ROTARY_ENCODER_LOW_RES =
+            "android.hardware.rotaryencoder.lowres";
+
     /** @hide */
     public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
 
diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java
index 8503072..38fbd72 100644
--- a/core/java/android/credentials/CredentialProviderInfo.java
+++ b/core/java/android/credentials/CredentialProviderInfo.java
@@ -128,6 +128,11 @@
     @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;
     }
 
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/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 f347909..a4593be 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -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/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/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/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/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/provider/Settings.java b/core/java/android/provider/Settings.java
index b19a034..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 =
@@ -12375,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.
          *
@@ -12388,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.
          *
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/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 1cfff14..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,7 +1999,7 @@
 
         /**
          * 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;
@@ -2022,8 +2020,7 @@
         }
 
         /**
-         * Returns a list of smart replies that can be added by the
-         * {@link NotificationAssistantService}
+         * 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;
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/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 8862f1d..a0cd074 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -1274,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/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/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 658341c..2f3d73a 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -263,7 +263,7 @@
     private static native void nativeSetDefaultFrameRateCompatibility(long transactionObj,
             long nativeObject, int compatibility);
     private static native void nativeSetFrameRateCategory(
-            long transactionObj, long nativeObject, int category);
+            long transactionObj, long nativeObject, int category, boolean smoothSwitchOnly);
     private static native void nativeSetFrameRateSelectionStrategy(
             long transactionObj, long nativeObject, int strategy);
     private static native long nativeGetHandle(long nativeObject);
@@ -3709,6 +3709,10 @@
          * @param sc The SurfaceControl to specify the frame rate category of.
          * @param category The frame rate category of this surface. The category value may influence
          * the system's choice of display frame rate.
+         * @param smoothSwitchOnly Set to {@code true} to indicate the display frame rate should not
+         * change if changing it would cause jank. Else {@code false}.
+         * This parameter is ignored when {@code category} is
+         * {@link Surface#FRAME_RATE_CATEGORY_DEFAULT}.
          *
          * @return This transaction object.
          *
@@ -3717,10 +3721,10 @@
          * @hide
          */
         @NonNull
-        public Transaction setFrameRateCategory(
-                @NonNull SurfaceControl sc, @Surface.FrameRateCategory int category) {
+        public Transaction setFrameRateCategory(@NonNull SurfaceControl sc,
+                @Surface.FrameRateCategory int category, boolean smoothSwitchOnly) {
             checkPreconditions(sc);
-            nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category);
+            nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category, smoothSwitchOnly);
             return this;
         }
 
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/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/SystemPerformanceHinter.java b/core/java/android/window/SystemPerformanceHinter.java
index 07ac292..2736b68 100644
--- a/core/java/android/window/SystemPerformanceHinter.java
+++ b/core/java/android/window/SystemPerformanceHinter.java
@@ -211,7 +211,11 @@
                     session.displayId);
             mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
                     FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
-            mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH);
+            // smoothSwitchOnly is false to request a higher framerate, even if it means switching
+            // the display mode will cause would jank on non-VRR devices because keeping a lower
+            // refresh rate would mean a poorer user experience.
+            mTransaction.setFrameRateCategory(
+                    displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH, /* smoothSwitchOnly= */ false);
             transactionChanged = true;
             Trace.beginAsyncSection("PerfHint-framerate-" + session.displayId + "-"
                     + session.reason, session.traceCookie);
@@ -251,7 +255,11 @@
                     session.displayId);
             mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
                     FRAME_RATE_SELECTION_STRATEGY_SELF);
-            mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT);
+            // smoothSwitchOnly is false to request a higher framerate, even if it means switching
+            // the display mode will cause would jank on non-VRR devices because keeping a lower
+            // refresh rate would mean a poorer user experience.
+            mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT,
+                    /* smoothSwitchOnly= */ false);
             transactionChanged = true;
             Trace.endAsyncSection("PerfHint-framerate-" + session.displayId + "-" + session.reason,
                     session.traceCookie);
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index d2a16a3..61f340a 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -29,6 +29,7 @@
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
@@ -361,6 +362,15 @@
     }
 
     /**
+     * Whether this transition contains any changes to the window hierarchy,
+     * including keyguard visibility.
+     */
+    public boolean hasChangesOrSideEffects() {
+        return !mChanges.isEmpty() || isKeyguardGoingAway()
+                || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0;
+    }
+
+    /**
      * Whether this transition includes keyguard going away.
      */
     public boolean isKeyguardGoingAway() {
diff --git a/core/java/android/window/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/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 7c931cd..392aa1b 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -13,3 +13,11 @@
   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"
+  is_fixed_read_only: true
+}
\ No newline at end of file
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/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/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index ba644eb..178c0d0 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -963,10 +963,11 @@
 }
 
 static void nativeSetFrameRateCategory(JNIEnv* env, jclass clazz, jlong transactionObj,
-                                       jlong nativeObject, jint category) {
+                                       jlong nativeObject, jint category,
+                                       jboolean smoothSwitchOnly) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
     const auto ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
-    transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category));
+    transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category), smoothSwitchOnly);
 }
 
 static void nativeSetFrameRateSelectionStrategy(JNIEnv* env, jclass clazz, jlong transactionObj,
@@ -2181,7 +2182,7 @@
             (void*)nativeSetFrameRate },
     {"nativeSetDefaultFrameRateCompatibility", "(JJI)V",
             (void*)nativeSetDefaultFrameRateCompatibility},
-    {"nativeSetFrameRateCategory", "(JJI)V",
+    {"nativeSetFrameRateCategory", "(JJIZ)V",
             (void*)nativeSetFrameRateCategory},
     {"nativeSetFrameRateSelectionStrategy", "(JJI)V",
             (void*)nativeSetFrameRateSelectionStrategy},
diff --git a/core/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 ca768ad..88b578b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6122,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" />
 
@@ -7235,13 +7237,23 @@
 
     <!-- @SystemApi Required for the privileged assistant apps targeting
          {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
-         that receive voice trigger from the trusted hotword detection service.
+         that receive voice trigger from a 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/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/symbols.xml b/core/res/res/values/symbols.xml
index c1ecb05..76744ea 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5124,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/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index 0941a2b..6c14ee3 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -137,7 +137,7 @@
             );
         });
 
-        PollingCheck.waitFor(/* timeout= */ 7000, () -> {
+        PollingCheck.waitFor(/* timeout= */ 10000, () -> {
             AtomicBoolean isActivityAtCorrectScale = new AtomicBoolean(false);
             rule.getScenario().onActivity(activity ->
                     isActivityAtCorrectScale.set(
@@ -163,7 +163,7 @@
         });
 
         PollingCheck.waitFor(
-                /* timeout= */ 5000,
+                /* timeout= */ 10000,
                 () -> InstrumentationRegistry.getInstrumentation()
                                         .getContext()
                                         .getResources()
diff --git a/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java b/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java
deleted file mode 100644
index e1f9523..0000000
--- a/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os.health;
-
-import static androidx.test.InstrumentationRegistry.getContext;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.os.ConditionVariable;
-import android.os.PowerMonitor;
-import android.os.PowerMonitorReadings;
-
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class SystemHealthManagerTest {
-    private List<PowerMonitor> mPowerMonitorInfo;
-    private PowerMonitorReadings mReadings;
-    private RuntimeException mException;
-
-    @Test
-    public void getPowerMonitors() {
-        SystemHealthManager shm = getContext().getSystemService(SystemHealthManager.class);
-        List<PowerMonitor> powerMonitorInfo = shm.getSupportedPowerMonitors();
-        assertThat(powerMonitorInfo).isNotNull();
-        if (powerMonitorInfo.isEmpty()) {
-            // This device does not support PowerStats HAL
-            return;
-        }
-
-        PowerMonitor consumerMonitor = null;
-        PowerMonitor measurementMonitor = null;
-        for (PowerMonitor pmi : powerMonitorInfo) {
-            if (pmi.type == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
-                measurementMonitor = pmi;
-            } else {
-                consumerMonitor = pmi;
-            }
-        }
-
-        List<PowerMonitor> selectedMonitors = new ArrayList<>();
-        if (consumerMonitor != null) {
-            selectedMonitors.add(consumerMonitor);
-        }
-        if (measurementMonitor != null) {
-            selectedMonitors.add(measurementMonitor);
-        }
-
-        PowerMonitorReadings readings = shm.getPowerMonitorReadings(selectedMonitors);
-
-        for (PowerMonitor monitor : selectedMonitors) {
-            assertThat(readings.getConsumedEnergyUws(monitor)).isAtLeast(0);
-            assertThat(readings.getTimestamp(monitor)).isGreaterThan(0);
-        }
-    }
-
-    @Test
-    public void getPowerMonitorsAsync() {
-        SystemHealthManager shm = getContext().getSystemService(SystemHealthManager.class);
-        ConditionVariable done = new ConditionVariable();
-        shm.getSupportedPowerMonitors(null, pms -> {
-            mPowerMonitorInfo = pms;
-            done.open();
-        });
-        done.block();
-        assertThat(mPowerMonitorInfo).isNotNull();
-        if (mPowerMonitorInfo.isEmpty()) {
-            // This device does not support PowerStats HAL
-            return;
-        }
-
-        PowerMonitor consumerMonitor = null;
-        PowerMonitor measurementMonitor = null;
-        for (PowerMonitor pmi : mPowerMonitorInfo) {
-            if (pmi.type == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
-                measurementMonitor = pmi;
-            } else {
-                consumerMonitor = pmi;
-            }
-        }
-
-        List<PowerMonitor> selectedMonitors = new ArrayList<>();
-        if (consumerMonitor != null) {
-            selectedMonitors.add(consumerMonitor);
-        }
-        if (measurementMonitor != null) {
-            selectedMonitors.add(measurementMonitor);
-        }
-
-        done.close();
-        shm.getPowerMonitorReadings(selectedMonitors, null,
-                readings -> {
-                    mReadings = readings;
-                    done.open();
-                },
-                exception -> {
-                    mException = exception;
-                    done.open();
-                }
-        );
-        done.block();
-
-        assertThat(mException).isNull();
-
-        for (PowerMonitor monitor : selectedMonitors) {
-            assertThat(mReadings.getConsumedEnergyUws(monitor)).isAtLeast(0);
-            assertThat(mReadings.getTimestamp(monitor)).isGreaterThan(0);
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
index 263e563..6229530 100644
--- a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
+++ b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
@@ -31,6 +31,7 @@
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -154,7 +155,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).applyAsyncUnsafe();
     }
 
@@ -171,7 +173,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).applyAsyncUnsafe();
     }
 
@@ -241,7 +244,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).setEarlyWakeupStart();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
@@ -261,7 +265,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).setEarlyWakeupEnd();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
@@ -281,7 +286,8 @@
                     eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
             verify(mTransaction).setFrameRateCategory(
                     eq(mDefaultDisplayRoot),
-                    eq(FRAME_RATE_CATEGORY_DEFAULT));
+                    eq(FRAME_RATE_CATEGORY_DEFAULT),
+                    eq(false));
             verify(mTransaction).setEarlyWakeupEnd();
             verify(mTransaction).applyAsyncUnsafe();
             verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
@@ -299,7 +305,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).setEarlyWakeupStart();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
@@ -310,7 +317,7 @@
                 mHinter.startSession(HINT_ALL, DEFAULT_DISPLAY_ID, TEST_OTHER_REASON);
         // Verify we never call SF and perf manager since session1 is already running
         verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt());
-        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt());
+        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean());
         verify(mTransaction, never()).setEarlyWakeupEnd();
         verify(mTransaction, never()).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
@@ -318,7 +325,7 @@
         session2.close();
         // Verify we have not cleaned up because session1 is still running
         verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt());
-        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt());
+        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean());
         verify(mTransaction, never()).setEarlyWakeupEnd();
         verify(mTransaction, never()).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
@@ -330,7 +337,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).setEarlyWakeupEnd();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
@@ -348,7 +356,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction).setEarlyWakeupStart();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
@@ -363,7 +372,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
         verify(mTransaction).setFrameRateCategory(
                 eq(mSecondaryDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_HIGH));
+                eq(FRAME_RATE_CATEGORY_HIGH),
+                eq(false));
         verify(mTransaction, never()).setEarlyWakeupStart();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
@@ -378,13 +388,15 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mDefaultDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction, never()).setFrameRateSelectionStrategy(
                 eq(mSecondaryDisplayRoot),
                 anyInt());
         verify(mTransaction, never()).setFrameRateCategory(
                 eq(mSecondaryDisplayRoot),
-                anyInt());
+                anyInt(),
+                eq(false));
         verify(mTransaction, never()).setEarlyWakeupEnd();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession, never()).sendHint(anyInt());
@@ -401,7 +413,8 @@
                 eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
         verify(mTransaction).setFrameRateCategory(
                 eq(mSecondaryDisplayRoot),
-                eq(FRAME_RATE_CATEGORY_DEFAULT));
+                eq(FRAME_RATE_CATEGORY_DEFAULT),
+                eq(false));
         verify(mTransaction).setEarlyWakeupEnd();
         verify(mTransaction).applyAsyncUnsafe();
         verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
diff --git a/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/graphics/java/Android.bp b/graphics/java/Android.bp
index 63d1f6d..db37a387 100644
--- a/graphics/java/Android.bp
+++ b/graphics/java/Android.bp
@@ -7,6 +7,12 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+aconfig_declarations {
+    name: "framework_graphics_flags",
+    package: "com.android.graphics.flags",
+    srcs: ["android/framework_graphics.aconfig"],
+}
+
 filegroup {
     name: "framework-graphics-nonupdatable-sources",
     srcs: [
diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig
new file mode 100644
index 0000000..e030dad
--- /dev/null
+++ b/graphics/java/android/framework_graphics.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.graphics.flags"
+
+flag {
+     name: "exact_compute_bounds"
+     namespace: "framework_graphics"
+     description: "Add a function without unused exact param for computeBounds."
+     bug: "304478551"
+}
\ No newline at end of file
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index b5fb13d..0a6fb84 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -16,11 +16,14 @@
 
 package android.graphics;
 
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.graphics.hwui.flags.Flags;
+
 import libcore.util.NativeAllocationRegistry;
 
 /**
@@ -125,6 +128,7 @@
      * Creates a new gainmap using the provided gainmap as the metadata source and the provided
      * bitmap as the replacement for the gainmapContents
      */
+    @FlaggedApi(Flags.FLAG_GAINMAP_CONSTRUCTOR_WITH_METADATA)
     public Gainmap(@NonNull Gainmap gainmap, @NonNull Bitmap gainmapContents) {
         this(gainmapContents, nCreateCopy(gainmap.mNativePtr));
     }
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index db1cc44..9fde0fd 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1609,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
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/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/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/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/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index b9c9049..da83d4c0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -1169,7 +1169,7 @@
     }
 
     @Test
-    public void testEmptyTransitionStillReportsKeyguardGoingAway() {
+    public void testEmptyTransition_withKeyguardGoingAway_plays() {
         Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
 
@@ -1188,6 +1188,65 @@
     }
 
     @Test
+    public void testSleepTransition_withKeyguardGoingAway_plays(){
+        Transitions transitions = createTestTransitions();
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        IBinder transitToken = new Binder();
+        transitions.requestStartTransition(transitToken,
+                new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */));
+
+        // Make a no-op transition
+        TransitionInfo info = new TransitionInfoBuilder(
+                TRANSIT_SLEEP, TRANSIT_FLAG_KEYGUARD_GOING_AWAY, true /* noOp */).build();
+        transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+                new StubTransaction());
+
+        // If keyguard-going-away flag set, then it shouldn't be aborted.
+        assertEquals(1, mDefaultHandler.activeCount());
+    }
+
+    @Test
+    public void testSleepTransition_withChanges_plays(){
+        Transitions transitions = createTestTransitions();
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        IBinder transitToken = new Binder();
+        transitions.requestStartTransition(transitToken,
+                new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */));
+
+        // Make a transition with some changes
+        TransitionInfo info = new TransitionInfoBuilder(TRANSIT_SLEEP)
+                .addChange(TRANSIT_OPEN).build();
+        info.setTrack(0);
+        transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+                new StubTransaction());
+
+        // If there is an actual change, then it shouldn't be aborted.
+        assertEquals(1, mDefaultHandler.activeCount());
+    }
+
+
+    @Test
+    public void testSleepTransition_empty_SyncBySleepHandler() {
+        Transitions transitions = createTestTransitions();
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        IBinder transitToken = new Binder();
+        transitions.requestStartTransition(transitToken,
+                new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */));
+
+        // Make a no-op transition
+        TransitionInfo info = new TransitionInfoBuilder(
+                TRANSIT_SLEEP, 0x0, true /* noOp */).build();
+        transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+                new StubTransaction());
+
+        // If there is nothing to actually play, it should not be offered to handlers.
+        assertEquals(0, mDefaultHandler.activeCount());
+    }
+
+    @Test
     public void testMultipleTracks() {
         Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
diff --git a/libs/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/api/current.txt b/libs/hwui/api/current.txt
index 7940821..c396a20 100644
--- a/libs/hwui/api/current.txt
+++ b/libs/hwui/api/current.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android.graphics {
 
   public class ColorMatrix {
diff --git a/libs/hwui/api/module-lib-current.txt b/libs/hwui/api/module-lib-current.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/module-lib-current.txt
+++ b/libs/hwui/api/module-lib-current.txt
@@ -1,3 +1 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/module-lib-removed.txt b/libs/hwui/api/module-lib-removed.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/module-lib-removed.txt
+++ b/libs/hwui/api/module-lib-removed.txt
@@ -1,3 +1 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/removed.txt b/libs/hwui/api/removed.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/removed.txt
+++ b/libs/hwui/api/removed.txt
@@ -1,3 +1 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/system-current.txt b/libs/hwui/api/system-current.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/system-current.txt
+++ b/libs/hwui/api/system-current.txt
@@ -1,3 +1 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/system-removed.txt b/libs/hwui/api/system-removed.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/system-removed.txt
+++ b/libs/hwui/api/system-removed.txt
@@ -1,3 +1 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
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/AudioFormat.java b/media/java/android/media/AudioFormat.java
index ceb3858..b002bbf 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -1282,10 +1282,8 @@
          *    {@link AudioFormat#CHANNEL_OUT_BACK_CENTER},
          *    {@link AudioFormat#CHANNEL_OUT_SIDE_LEFT},
          *    {@link AudioFormat#CHANNEL_OUT_SIDE_RIGHT}.
-         *    <p> For a valid {@link AudioTrack} channel position mask,
-         *    the following conditions apply:
-         *    <br> (1) at most eight channel positions may be used;
-         *    <br> (2) right/left pairs should be matched.
+         *    <p> For output or {@link AudioTrack}, channel position masks which do not contain
+         *    matched left/right pairs are invalid.
          *    <p> For input or {@link AudioRecord}, the mask should be
          *    {@link AudioFormat#CHANNEL_IN_MONO} or
          *    {@link AudioFormat#CHANNEL_IN_STEREO}.  {@link AudioFormat#CHANNEL_IN_MONO} is
diff --git a/media/java/android/media/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/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index e9a6ed4..9ced2a4 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -16,6 +16,7 @@
 
 package android.media.audiopolicy;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -48,6 +49,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.media.audio.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -417,6 +419,7 @@
      * @return {@link AudioManager#SUCCESS} if the update was successful,
      *  {@link AudioManager#ERROR} otherwise.
      */
+    @FlaggedApi(Flags.FLAG_AUDIO_POLICY_UPDATE_MIXING_RULES_API)
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     public int updateMixingRules(
             @NonNull List<Pair<AudioMix, AudioMixingRule>> mixingRuleUpdates) {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index a354f91..c9cfa67 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -27,3 +27,10 @@
     description: "Disables the broadcast receiver that prevents scanning when the screen is off."
     bug: "304234628"
 }
+
+flag {
+    namespace: "media_solutions"
+    name: "fallback_to_default_handling_when_media_session_has_fixed_volume_handling"
+    description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller."
+    bug: "293743975"
+}
diff --git a/media/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/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/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/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/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/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 9b7c80b..9d3200d 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -434,6 +434,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/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 0623d4a..03f7c99 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -144,9 +144,32 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
         {
+          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        },
+        {
           "exclude-annotation": "android.platform.test.annotations.Postsubmit"
         }
       ]
     }
+  ],
+  // v2/sysui/suite/test-mapping-sysui-screenshot-test-staged
+  "sysui-screenshot-test-staged": [
+    {
+      "name": "SystemUIGoogleScreenshotTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "include-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "include-annotation": "android.platform.test.annotations.FlakyTest"
+        },
+        {
+          "include-annotation": "android.platform.test.annotations.Postsubmit"
+        }
+      ]
+    }
   ]
 }
diff --git a/packages/SystemUI/communal/layout/tests/Android.bp b/packages/SystemUI/communal/layout/tests/Android.bp
index a60b1de..9a05504 100644
--- a/packages/SystemUI/communal/layout/tests/Android.bp
+++ b/packages/SystemUI/communal/layout/tests/Android.bp
@@ -32,7 +32,7 @@
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
         "testables",
-        "truth-prebuilt",
+        "truth",
     ],
     libs: [
         "android.test.mock",
diff --git a/packages/SystemUI/compose/features/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/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/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/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/values/config.xml b/packages/SystemUI/res/values/config.xml
index 6856717..75e71e4 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -957,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 6377df3..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>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 321594f..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] -->
diff --git a/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml b/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml
new file mode 100644
index 0000000..4b430e1
--- /dev/null
+++ b/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+</network-capabilities-declaration>
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
index 3360c96..aef8371 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -24,7 +24,7 @@
     val knownFlags: Map<String, Flag<*>>
         get() {
             // We need to access Flags in order to initialize our map.
-            assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+            assert(flagMap.contains(Flags.NULL_FLAG.name)) { "Where is the null flag?" }
             return flagMap
         }
 
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
index 75465c2..f4b4296 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
@@ -24,7 +24,7 @@
     val knownFlags: Map<String, Flag<*>>
         get() {
             // We need to access Flags in order to initialize our map.
-            assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+            assert(flagMap.contains(Flags.NULL_FLAG.name)) { "Where is the null flag?" }
             return flagMap
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 01a75d9..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
@@ -37,7 +39,7 @@
 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
@@ -61,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
@@ -297,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)
@@ -342,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 {
@@ -454,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/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 79642bd..758d1fe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -54,6 +54,9 @@
 import com.android.systemui.flags.FeatureFlags;
 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.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;
@@ -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
@@ -199,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() {
@@ -266,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 b7bb35e..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;
@@ -1984,6 +1984,7 @@
                 @Override
                 public void onAuthenticationAcquired(int acquireInfo) {
                     Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationAcquired");
+                    mLogger.logFingerprintAcquired(acquireInfo);
                     handleFingerprintAcquired(acquireInfo);
                     Trace.endSection();
                 }
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/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index fe19616..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
@@ -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/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/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/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
index 6946950..1c2ff4b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
@@ -36,6 +36,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.systemui.FeatureFlags;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.util.settings.GlobalSettings;
@@ -80,6 +81,7 @@
     private final Map<String, Boolean> mBooleanFlagCache = new ConcurrentHashMap<>();
     private final Map<String, String> mStringFlagCache = new ConcurrentHashMap<>();
     private final Map<String, Integer> mIntFlagCache = new ConcurrentHashMap<>();
+    private final FeatureFlags mGantryFlags;
     private final Restarter mRestarter;
 
     private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
@@ -124,6 +126,7 @@
             @Main Resources resources,
             ServerFlagReader serverFlagReader,
             @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags,
+            FeatureFlags gantryFlags,
             Restarter restarter) {
         mFlagManager = flagManager;
         mContext = context;
@@ -132,6 +135,7 @@
         mSystemProperties = systemProperties;
         mServerFlagReader = serverFlagReader;
         mAllFlags = allFlags;
+        mGantryFlags = gantryFlags;
         mRestarter = restarter;
     }
 
@@ -259,9 +263,8 @@
         if (!hasServerOverride
                 && !defaultValue
                 && result == null
-                && !flag.getName().equals(Flags.TEAMFOOD.getName())
                 && flag.getTeamfood()) {
-            return isEnabled(Flags.TEAMFOOD);
+            return mGantryFlags.sysuiTeamfood();
         }
 
         return result == null ? mServerFlagReader.readServerOverride(
@@ -534,7 +537,7 @@
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("can override: true");
-
+        pw.println("teamfood: " + mGantryFlags.sysuiTeamfood());
         pw.println("booleans: " + mBooleanFlagCache.size());
         // Sort our flags for dumping
         TreeMap<String, Boolean> dumpBooleanMap = new TreeMap<>(mBooleanFlagCache);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 11ac39f..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")
 
@@ -620,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
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
index 3fe6806..7ccc26c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
@@ -22,11 +22,11 @@
 /**
  * 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
+ * * [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.
- * * [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.
+ * * [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,
@@ -60,13 +60,13 @@
      * Example usage:
      * ```
      * public void setController(NotificationShelfController notificationShelfController) {
-     *     mShelfRefactor.assertDisabled();
+     *     mShelfRefactor.assertInLegacyMode();
      *     mController = notificationShelfController;
      * }
      * ````
      */
-    fun assertDisabled() =
-        check(!isEnabled) { "Code path not supported when $flagName is enabled." }
+    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
@@ -76,18 +76,17 @@
      * Example usage:
      * ```
      * public void setShelfIcons(NotificationIconContainer icons) {
-     *     if (mShelfRefactor.expectEnabled()) {
-     *         mShelfIcons = icons;
-     *     }
+     *     if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
+     *     mShelfIcons = icons;
      * }
      * ```
      */
-    fun expectEnabled(): Boolean {
+    fun isUnexpectedlyInLegacyMode(): Boolean {
         if (!isEnabled) {
-            val message = "Code path not supported when $flagName is disabled."
+            val message = "New code path expects $flagName to be enabled."
             Log.wtf(TAG, message, Exception(message))
         }
-        return isEnabled
+        return !isEnabled
     }
 
     companion object {
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/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/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 660bd84..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
@@ -355,6 +355,7 @@
                 when (toState) {
                     KeyguardState.DREAMING -> TO_DREAMING_DURATION
                     KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
+                    KeyguardState.AOD -> TO_AOD_DURATION
                     else -> DEFAULT_DURATION
                 }.inWholeMilliseconds
         }
@@ -364,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/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/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/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 4a2954d..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
@@ -322,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,
@@ -333,6 +333,7 @@
                 keyguardStateController,
                 shadeInteractor,
                 null, // clock provider only needed for burn in
+                null, // jank monitor not required for preview mode
             )
         )
         rootView.addView(
@@ -342,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/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/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/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 5c6e902..a698208 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -38,7 +38,6 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.DialogCuj;
 import com.android.systemui.animation.DialogLaunchAnimator;
@@ -53,12 +52,13 @@
 import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.statusbar.connectivity.WifiIndicators;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor;
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
+import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel;
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
 import com.android.systemui.statusbar.policy.HotspotController;
@@ -85,10 +85,9 @@
     private final NetworkController mNetworkController;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
     private final Callback mCallback = new Callback();
-    private final WifiInteractor mWifiInteractor;
     private final TileJavaAdapter mJavaAdapter;
     private final FeatureFlags mFeatureFlags;
-    private boolean mWifiConnected;
+    private boolean mCastTransportAllowed;
     private boolean mHotspotConnected;
 
     @Inject
@@ -107,7 +106,7 @@
             NetworkController networkController,
             HotspotController hotspotController,
             DialogLaunchAnimator dialogLaunchAnimator,
-            WifiInteractor wifiInteractor,
+            ConnectivityRepository connectivityRepository,
             TileJavaAdapter javaAdapter,
             FeatureFlags featureFlags
     ) {
@@ -117,7 +116,6 @@
         mKeyguard = keyguardStateController;
         mNetworkController = networkController;
         mDialogLaunchAnimator = dialogLaunchAnimator;
-        mWifiInteractor = wifiInteractor;
         mJavaAdapter = javaAdapter;
         mFeatureFlags = featureFlags;
         mController.observe(this, mCallback);
@@ -125,7 +123,11 @@
         if (!mFeatureFlags.isEnabled(SIGNAL_CALLBACK_DEPRECATION)) {
             mNetworkController.observe(this, mSignalCallback);
         } else {
-            mJavaAdapter.bind(this, mWifiInteractor.getWifiNetwork(), mNetworkModelConsumer);
+            mJavaAdapter.bind(
+                    this,
+                    connectivityRepository.getDefaultConnections(),
+                    mNetworkModelConsumer
+            );
         }
         hotspotController.observe(this, mHotspotCallback);
     }
@@ -282,7 +284,7 @@
         }
         state.icon = ResourceIcon.get(state.value ? R.drawable.ic_cast_connected
                 : R.drawable.ic_cast);
-        if (canCastToWifi() || state.value) {
+        if (canCastToNetwork() || state.value) {
             state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
             if (!state.value) {
                 state.secondaryLabel = "";
@@ -291,7 +293,7 @@
             state.forceExpandIcon = willPopDialog();
         } else {
             state.state = Tile.STATE_UNAVAILABLE;
-            String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi);
+            String noWifi = mContext.getString(R.string.quick_settings_cast_no_network);
             state.secondaryLabel = noWifi;
             state.forceExpandIcon = false;
         }
@@ -308,13 +310,13 @@
                 : mContext.getString(R.string.quick_settings_cast_device_default_name);
     }
 
-    private boolean canCastToWifi() {
-        return mWifiConnected || mHotspotConnected;
+    private boolean canCastToNetwork() {
+        return mCastTransportAllowed || mHotspotConnected;
     }
 
-    private void setWifiConnected(boolean connected) {
-        if (connected != mWifiConnected) {
-            mWifiConnected = connected;
+    private void setCastTransportAllowed(boolean connected) {
+        if (connected != mCastTransportAllowed) {
+            mCastTransportAllowed = connected;
             // Hotspot is not connected, so changes here should update
             if (!mHotspotConnected) {
                 refreshState();
@@ -326,14 +328,17 @@
         if (connected != mHotspotConnected) {
             mHotspotConnected = connected;
             // Wifi is not connected, so changes here should update
-            if (!mWifiConnected) {
+            if (!mCastTransportAllowed) {
                 refreshState();
             }
         }
     }
 
-    private final Consumer<WifiNetworkModel> mNetworkModelConsumer = (model) -> {
-        setWifiConnected(model instanceof WifiNetworkModel.Active);
+    private final Consumer<DefaultConnectionModel> mNetworkModelConsumer = (model) -> {
+        boolean isWifiDefault = model.getWifi().isDefault();
+        boolean isEthernetDefault = model.getEthernet().isDefault();
+        boolean hasCellularTransport = model.getMobile().isDefault();
+        setCastTransportAllowed((isWifiDefault || isEthernetDefault) && !hasCellularTransport);
     };
 
     private final SignalCallback mSignalCallback = new SignalCallback() {
@@ -342,7 +347,7 @@
                     // statusIcon.visible has the connected status information
                     boolean enabledAndConnected = indicators.enabled
                             && (indicators.qsIcon != null && indicators.qsIcon.visible);
-                    setWifiConnected(enabledAndConnected);
+                    setCastTransportAllowed(enabledAndConnected);
                 }
             };
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index a1d5d98..ade93b1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -186,14 +186,14 @@
         private fun createOptionList(): List<ScreenShareOption> {
             return listOf(
                 ScreenShareOption(
-                    ENTIRE_SCREEN,
-                    R.string.screen_share_permission_dialog_option_entire_screen,
-                    R.string.screenrecord_permission_dialog_warning_entire_screen
-                ),
-                ScreenShareOption(
                     SINGLE_APP,
                     R.string.screen_share_permission_dialog_option_single_app,
                     R.string.screenrecord_permission_dialog_warning_single_app
+                ),
+                ScreenShareOption(
+                    ENTIRE_SCREEN,
+                    R.string.screen_share_permission_dialog_option_entire_screen,
+                    R.string.screenrecord_permission_dialog_warning_entire_screen
                 )
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/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 127a57e..21a08a9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -325,7 +325,7 @@
             Context context,
             FeatureFlags flags,
             ScreenshotSmartActions screenshotSmartActions,
-            ScreenshotNotificationsController screenshotNotificationsController,
+            ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
             ScrollCaptureClient scrollCaptureClient,
             UiEventLogger uiEventLogger,
             ImageExporter imageExporter,
@@ -346,7 +346,7 @@
             @Assisted boolean showUIOnExternalDisplay
     ) {
         mScreenshotSmartActions = screenshotSmartActions;
-        mNotificationsController = screenshotNotificationsController;
+        mNotificationsController = screenshotNotificationsControllerFactory.create(displayId);
         mScrollCaptureClient = scrollCaptureClient;
         mUiEventLogger = uiEventLogger;
         mImageExporter = imageExporter;
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 abe40ff..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> {
@@ -140,6 +172,12 @@
         }
     }
 
+    private fun getNotificationController(id: Int): ScreenshotNotificationsController {
+        return notificationControllers.computeIfAbsent(id) {
+            screenshotNotificationControllerFactory.create(id)
+        }
+    }
+
     /** For java compatibility only. see [executeScreenshots] */
     fun executeScreenshotsAsync(
         screenshotRequest: ScreenshotRequest,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 75d52cb..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;
@@ -246,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);
@@ -264,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/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 51972c2..8dc97c0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1446,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);
         }
 
@@ -2358,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();
@@ -4443,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(
@@ -4456,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);
             }
 
@@ -4562,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/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 25bd8e7..cb95b25 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -38,7 +38,6 @@
 import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
 import com.android.systemui.Dumpable
-import com.android.systemui.res.R
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
@@ -49,6 +48,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.ChipVisibilityListener
 import com.android.systemui.qs.HeaderPrivacyIconsController
+import com.android.systemui.res.R
 import com.android.systemui.shade.ShadeHeaderController.Companion.HEADER_TRANSITION_ID
 import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
 import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
@@ -304,7 +304,8 @@
 
         iconManager = tintedIconManagerFactory.create(iconContainer, StatusBarLocation.QS)
         iconManager.setTint(
-            Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
+            Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary),
+            Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimaryInverse),
         )
 
         carrierIconSlots =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 2147510..f32f1c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -16,9 +16,16 @@
 package com.android.systemui.statusbar;
 
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.os.UserHandle.USER_NULL;
+import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
+import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
 import android.app.ActivityOptions;
 import android.app.KeyguardManager;
 import android.app.Notification;
@@ -30,8 +37,10 @@
 import android.content.IntentSender;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerExecutor;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -41,14 +50,18 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
 
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.recents.OverviewProxyService;
@@ -63,7 +76,9 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -84,6 +99,11 @@
     private final SecureSettings mSecureSettings;
     private final Object mLock = new Object();
 
+    private static final Uri SHOW_LOCKSCREEN =
+            Settings.Secure.getUriFor(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+    private static final Uri SHOW_PRIVATE_LOCKSCREEN =
+            Settings.Secure.getUriFor(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
     private final Lazy<NotificationVisibilityProvider> mVisibilityProviderLazy;
     private final Lazy<CommonNotifCollection> mCommonNotifCollectionLazy;
     private final DevicePolicyManager mDevicePolicyManager;
@@ -91,6 +111,23 @@
     private final SparseBooleanArray mUsersWithSeparateWorkChallenge = new SparseBooleanArray();
     private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
     private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
+
+    // The variables between mUsersDpcAllowingNotifications and
+    // mUsersUsersAllowingPrivateNotifications (inclusive) are written on a background thread
+    // and read on the main thread. Because the pipeline needs these values, adding locks would
+    // introduce too much jank. This means that some pipeline runs could get incorrect values, that
+    // would be fixed on the next pipeline run. We think this will be rare since a pipeline run
+    // would have to overlap with a DPM sync or a user changing a value in Settings, and we run the
+    // pipeline frequently enough that it should be corrected by the next time it matters for the
+    // user.
+    private final SparseBooleanArray mUsersDpcAllowingNotifications = new SparseBooleanArray();
+    private final SparseBooleanArray mUsersUsersAllowingNotifications = new SparseBooleanArray();
+    private boolean mKeyguardAllowingNotifications = true;
+    private final SparseBooleanArray mUsersDpcAllowingPrivateNotifications
+            = new SparseBooleanArray();
+    private final SparseBooleanArray mUsersUsersAllowingPrivateNotifications
+            = new SparseBooleanArray();
+
     private final SparseBooleanArray mUsersInLockdownLatestResult = new SparseBooleanArray();
     private final SparseBooleanArray mShouldHideNotifsLatestResult = new SparseBooleanArray();
     private final UserManager mUserManager;
@@ -99,24 +136,39 @@
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final NotificationClickNotifier mClickNotifier;
     private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy;
+    private final FeatureFlagsClassic mFeatureFlags;
     private boolean mShowLockscreenNotifications;
     private LockPatternUtils mLockPatternUtils;
     protected KeyguardManager mKeyguardManager;
     private int mState = StatusBarState.SHADE;
     private final ListenerSet<NotificationStateChangedListener> mNotifStateChangedListeners =
             new ListenerSet<>();
+    private final Collection<Uri> mLockScreenUris = new ArrayList<>();
+
 
     protected final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
 
-            if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) &&
-                    isCurrentProfile(getSendingUserId())) {
-                mUsersAllowingPrivateNotifications.clear();
-                updateLockscreenNotificationSetting();
-                // TODO(b/231976036): Consolidate pipeline invalidations related to this event
-                // notifyNotificationStateChanged();
+            if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) {
+                if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+                    boolean changed = updateDpcSettings(getSendingUserId());
+                    if (mCurrentUserId == getSendingUserId()) {
+                        changed |= updateLockscreenNotificationSetting();
+                    }
+                    if (changed) {
+                        notifyNotificationStateChanged();
+                    }
+                } else {
+                    if (isCurrentProfile(getSendingUserId())) {
+                        mUsersAllowingPrivateNotifications.clear();
+                        updateLockscreenNotificationSetting();
+                        // TODO(b/231976036): Consolidate pipeline invalidations related to this
+                        //  event
+                        // notifyNotificationStateChanged();
+                    }
+                }
             }
         }
     };
@@ -136,6 +188,14 @@
                     updateCurrentProfilesCache();
                     break;
                 case Intent.ACTION_USER_ADDED:
+                    updateCurrentProfilesCache();
+                    if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+                        final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+                        mBackgroundHandler.post(() -> {
+                            initValuesForUser(userId);
+                        });
+                    }
+                    break;
                 case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
                 case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
                     updateCurrentProfilesCache();
@@ -193,6 +253,8 @@
 
     protected final Context mContext;
     private final Handler mMainHandler;
+    private final Handler mBackgroundHandler;
+    private final Executor mBackgroundExecutor;
     protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
     protected final SparseArray<UserInfo> mCurrentManagedProfiles = new SparseArray<>();
 
@@ -214,13 +276,18 @@
             KeyguardManager keyguardManager,
             StatusBarStateController statusBarStateController,
             @Main Handler mainHandler,
+            @Background Handler backgroundHandler,
+            @Background Executor backgroundExecutor,
             DeviceProvisionedController deviceProvisionedController,
             KeyguardStateController keyguardStateController,
             SecureSettings secureSettings,
             DumpManager dumpManager,
-            LockPatternUtils lockPatternUtils) {
+            LockPatternUtils lockPatternUtils,
+            FeatureFlagsClassic featureFlags) {
         mContext = context;
         mMainHandler = mainHandler;
+        mBackgroundHandler = backgroundHandler;
+        mBackgroundExecutor = backgroundExecutor;
         mDevicePolicyManager = devicePolicyManager;
         mUserManager = userManager;
         mUserTracker = userTracker;
@@ -236,6 +303,10 @@
         mDeviceProvisionedController = deviceProvisionedController;
         mSecureSettings = secureSettings;
         mKeyguardStateController = keyguardStateController;
+        mFeatureFlags = featureFlags;
+
+        mLockScreenUris.add(SHOW_LOCKSCREEN);
+        mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN);
 
         dumpManager.registerDumpable(this);
     }
@@ -243,16 +314,53 @@
     public void setUpWithPresenter(NotificationPresenter presenter) {
         mPresenter = presenter;
 
-        mLockscreenSettingsObserver = new ContentObserver(mMainHandler) {
+        mLockscreenSettingsObserver = new ContentObserver(
+                mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
+                        ? mBackgroundHandler
+                        : mMainHandler) {
+
             @Override
-            public void onChange(boolean selfChange) {
-                // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
-                // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
-                mUsersAllowingPrivateNotifications.clear();
-                mUsersAllowingNotifications.clear();
-                // ... and refresh all the notifications
-                updateLockscreenNotificationSetting();
-                notifyNotificationStateChanged();
+            public void onChange(boolean selfChange, Collection<Uri> uris, int flags) {
+                if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+                    @SuppressLint("MissingPermission")
+                    List<UserInfo> users = mUserManager.getUsers();
+                    for (int i = users.size() - 1; i >= 0; i--) {
+                        onChange(selfChange, uris, flags,users.get(i).getUserHandle());
+                    }
+                } else {
+                    // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
+                    // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
+                    mUsersAllowingPrivateNotifications.clear();
+                    mUsersAllowingNotifications.clear();
+                    // ... and refresh all the notifications
+                    updateLockscreenNotificationSetting();
+                    notifyNotificationStateChanged();
+                }
+            }
+
+            // Note: even though this is an override, this method is not called by the OS
+            // since we're not in system_server. We are using it internally for cases when
+            // we have a single user id available (e.g. from USER_ADDED).
+            @Override
+            public void onChange(boolean selfChange, Collection<Uri> uris,
+                    int flags, UserHandle user) {
+                if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+                    boolean changed = false;
+                    for (Uri uri: uris) {
+                        if (SHOW_LOCKSCREEN.equals(uri)) {
+                            changed |= updateUserShowSettings(user.getIdentifier());
+                        } else if (SHOW_PRIVATE_LOCKSCREEN.equals(uri)) {
+                            changed |= updateUserShowPrivateSettings(user.getIdentifier());
+                        }
+                    }
+
+                    if (mCurrentUserId == user.getIdentifier()) {
+                        changed |= updateLockscreenNotificationSetting();
+                    }
+                    if (changed) {
+                        notifyNotificationStateChanged();
+                    }
+                }
             }
         };
 
@@ -268,23 +376,26 @@
         };
 
         mContext.getContentResolver().registerContentObserver(
-                mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false,
+                SHOW_LOCKSCREEN, false,
                 mLockscreenSettingsObserver,
                 UserHandle.USER_ALL);
 
         mContext.getContentResolver().registerContentObserver(
-                mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
+                SHOW_PRIVATE_LOCKSCREEN,
                 true,
                 mLockscreenSettingsObserver,
                 UserHandle.USER_ALL);
 
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
-                mSettingsObserver);
+        if (!mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
+                    mSettingsObserver);
+        }
 
         mBroadcastDispatcher.registerReceiver(mAllUsersReceiver,
                 new IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
-                null /* handler */, UserHandle.ALL);
+                mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
+                        ? mBackgroundExecutor : null, UserHandle.ALL);
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_ADDED);
@@ -305,7 +416,23 @@
         mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late
         updateCurrentProfilesCache();
 
-        mSettingsObserver.onChange(false);  // set up
+        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            // Set  up
+            mBackgroundHandler.post(() -> {
+                @SuppressLint("MissingPermission") List<UserInfo> users = mUserManager.getUsers();
+                for (int i = users.size() - 1; i >= 0; i--) {
+                    initValuesForUser(users.get(i).id);
+                }
+            });
+        } else {
+            mSettingsObserver.onChange(false);  // set up
+        }
+    }
+
+    private void initValuesForUser(@UserIdInt int userId) {
+        mLockscreenSettingsObserver.onChange(
+                false, mLockScreenUris, 0, UserHandle.of(userId));
+        updateDpcSettings(userId);
     }
 
     public boolean shouldShowLockscreenNotifications() {
@@ -322,17 +449,75 @@
         mShowLockscreenNotifications = show;
     }
 
-    protected void updateLockscreenNotificationSetting() {
-        final boolean show = mSecureSettings.getIntForUser(
-                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
-                1,
-                mCurrentUserId) != 0;
-        final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
-                null /* admin */, mCurrentUserId);
-        final boolean allowedByDpm = (dpmFlags
-                & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+    protected boolean updateLockscreenNotificationSetting() {
+        boolean show;
+        boolean allowedByDpm;
 
+        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            show = mUsersUsersAllowingNotifications.get(mCurrentUserId)
+                    && mKeyguardAllowingNotifications;
+            // If DPC never notified us about a user, that means they have no policy for the user,
+            // and they allow the behavior
+            allowedByDpm = mUsersDpcAllowingNotifications.get(mCurrentUserId, true);
+        } else {
+            show = mSecureSettings.getIntForUser(
+                    LOCK_SCREEN_SHOW_NOTIFICATIONS,
+                    1,
+                    mCurrentUserId) != 0;
+            final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
+                    null /* admin */, mCurrentUserId);
+            allowedByDpm = (dpmFlags
+                    & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+        }
+
+        final boolean oldValue = mShowLockscreenNotifications;
         setShowLockscreenNotifications(show && allowedByDpm);
+
+        return oldValue != mShowLockscreenNotifications;
+    }
+
+    @WorkerThread
+    protected boolean updateDpcSettings(int userId) {
+        boolean originalAllowLockscreen = mUsersDpcAllowingNotifications.get(userId);
+        boolean originalAllowPrivate = mUsersDpcAllowingPrivateNotifications.get(userId);
+        final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
+                null /* admin */, userId);
+        final boolean allowedLockscreen = (dpmFlags & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+        final boolean allowedPrivate = (dpmFlags & KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0;
+        mUsersDpcAllowingNotifications.put(userId, allowedLockscreen);
+        mUsersDpcAllowingPrivateNotifications.put(userId, allowedPrivate);
+        return (originalAllowLockscreen != allowedLockscreen)
+                || (originalAllowPrivate != allowedPrivate);
+    }
+
+    @WorkerThread
+    private boolean updateUserShowSettings(int userId) {
+        boolean originalAllowLockscreen = mUsersUsersAllowingNotifications.get(userId);
+        boolean newAllowLockscreen = mSecureSettings.getIntForUser(
+                LOCK_SCREEN_SHOW_NOTIFICATIONS,
+                1,
+                userId) != 0;
+        mUsersUsersAllowingNotifications.put(userId, newAllowLockscreen);
+        boolean keyguardChanged = updateGlobalKeyguardSettings();
+        return (newAllowLockscreen != originalAllowLockscreen) || keyguardChanged;
+    }
+
+    @WorkerThread
+    private boolean updateUserShowPrivateSettings(int userId) {
+        boolean originalValue = mUsersUsersAllowingPrivateNotifications.get(userId);
+        boolean newValue = mSecureSettings.getIntForUser(
+                LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+                0,
+                userId) != 0;
+        mUsersUsersAllowingPrivateNotifications.put(userId, newValue);
+        return (newValue != originalValue);
+    }
+
+    @WorkerThread
+    private boolean updateGlobalKeyguardSettings() {
+        final boolean oldValue = mKeyguardAllowingNotifications;
+        mKeyguardAllowingNotifications = mKeyguardManager.getPrivateNotificationsAllowed();
+        return oldValue != mKeyguardAllowingNotifications;
     }
 
     /**
@@ -340,21 +525,41 @@
      * when the lockscreen is in "public" (secure & locked) mode?
      */
     public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
-        if (userHandle == UserHandle.USER_ALL) {
-            return true;
-        }
+        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            if (userHandle == UserHandle.USER_ALL) {
+                userHandle = mCurrentUserId;
+            }
+            if (mUsersUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+                // default value before moving to 'released'
+                Log.wtf(TAG, "Asking for redact notifs setting too early", new Throwable());
+                updateUserShowPrivateSettings(userHandle);
+            }
+            if (mUsersDpcAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+                // default value before moving to 'released'
+                Log.wtf(TAG, "Asking for redact notifs dpm override too early", new Throwable());
+                updateDpcSettings(userHandle);
+            }
+            return mUsersUsersAllowingPrivateNotifications.get(userHandle)
+                    && mUsersDpcAllowingPrivateNotifications.get(userHandle);
+        } else {
+            if (userHandle == UserHandle.USER_ALL) {
+                return true;
+            }
 
-        if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
-            final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
-                    Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
-            final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
-                    DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
-            final boolean allowed = allowedByUser && allowedByDpm;
-            mUsersAllowingPrivateNotifications.append(userHandle, allowed);
-            return allowed;
-        }
+            if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+                final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
+                        LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
+                final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
+                        KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+                final boolean allowed = allowedByUser && allowedByDpm;
+                mUsersAllowingPrivateNotifications.append(userHandle, allowed);
+                return allowed;
+            }
 
-        return mUsersAllowingPrivateNotifications.get(userHandle);
+            return mUsersAllowingPrivateNotifications.get(userHandle);
+        }
     }
 
     /**
@@ -406,21 +611,44 @@
      * "public" (secure & locked) mode?
      */
     public boolean userAllowsNotificationsInPublic(int userHandle) {
-        if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) {
-            return true;
-        }
+        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            // Unlike 'show private', settings does not show a copy of this setting for each
+            // profile, so it inherits from the parent user.
+            if (userHandle == UserHandle.USER_ALL || mCurrentManagedProfiles.contains(userHandle)) {
+                userHandle = mCurrentUserId;
+            }
+            if (mUsersUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
+                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+                // default value before moving to 'released'
+                Log.wtf(TAG, "Asking for show notifs setting too early", new Throwable());
+                updateUserShowSettings(userHandle);
+            }
+            if (mUsersDpcAllowingNotifications.indexOfKey(userHandle) < 0) {
+                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+                // default value before moving to 'released'
+                Log.wtf(TAG, "Asking for show notifs dpm override too early", new Throwable());
+                updateDpcSettings(userHandle);
+            }
+            return mUsersUsersAllowingNotifications.get(userHandle)
+                    && mUsersDpcAllowingNotifications.get(userHandle)
+                    && mKeyguardAllowingNotifications;
+        } else {
+            if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) {
+                return true;
+            }
 
-        if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
-            final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
-                    Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
-            final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
-                    DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
-            final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed();
-            final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem;
-            mUsersAllowingNotifications.append(userHandle, allowed);
-            return allowed;
+            if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
+                final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
+                        LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
+                final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
+                        KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+                final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed();
+                final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem;
+                mUsersAllowingNotifications.append(userHandle, allowed);
+                return allowed;
+            }
+            return mUsersAllowingNotifications.get(userHandle);
         }
-        return mUsersAllowingNotifications.get(userHandle);
     }
 
     /** @return true if the entry needs redaction when on the lockscreen. */
@@ -451,9 +679,15 @@
             return true;
         }
         NotificationEntry entry = mCommonNotifCollectionLazy.get().getEntry(key);
-        return entry != null
-                && entry.getRanking().getLockscreenVisibilityOverride() 
-                == Notification.VISIBILITY_PRIVATE;
+        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+            return entry != null
+                    && entry.getRanking().getChannel().getLockscreenVisibility()
+                    == Notification.VISIBILITY_PRIVATE;
+        } else {
+            return entry != null
+                    && entry.getRanking().getLockscreenVisibilityOverride()
+                    == Notification.VISIBILITY_PRIVATE;
+        }
     }
 
     private void updateCurrentProfilesCache() {
@@ -491,20 +725,6 @@
     }
 
     /**
-     * If any managed/work profiles are in public mode.
-     */
-    public boolean isAnyManagedProfilePublicMode() {
-        synchronized (mLock) {
-            for (int i = mCurrentManagedProfiles.size() - 1; i >= 0; i--) {
-                if (isLockscreenPublicMode(mCurrentManagedProfiles.valueAt(i).id)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
      * Returns the current user id. This can change if the user is switched.
      */
     public int getCurrentUserId() {
@@ -581,8 +801,16 @@
     }
 
     private void notifyNotificationStateChanged() {
-        for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
-            listener.onNotificationStateChanged();
+        if (!Looper.getMainLooper().isCurrentThread()) {
+            mMainHandler.post(() -> {
+                for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
+                    listener.onNotificationStateChanged();
+                }
+            });
+        } else {
+            for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
+                listener.onNotificationStateChanged();
+            }
         }
     }
 
@@ -620,5 +848,15 @@
         pw.println(mUsersInLockdownLatestResult);
         pw.print("  mShouldHideNotifsLatestResult=");
         pw.println(mShouldHideNotifsLatestResult);
+        pw.print("  mUsersDpcAllowingNotifications=");
+        pw.println(mUsersDpcAllowingNotifications);
+        pw.print("  mUsersUsersAllowingNotifications=");
+        pw.println(mUsersUsersAllowingNotifications);
+        pw.print("  mKeyguardAllowingNotifications=");
+        pw.println(mKeyguardAllowingNotifications);
+        pw.print("  mUsersDpcAllowingPrivateNotifications=");
+        pw.println(mUsersDpcAllowingPrivateNotifications);
+        pw.print("  mUsersUsersAllowingPrivateNotifications=");
+        pw.println(mUsersUsersAllowingPrivateNotifications);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/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 f8c049e..23b697e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -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/StatusIconDisplayable.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
index 1196211..595ab70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
@@ -21,6 +21,21 @@
 public interface StatusIconDisplayable extends DarkReceiver {
     String getSlot();
     void setStaticDrawableColor(int color);
+
+    /**
+     * For a layer drawable, or one that has a background, {@code tintColor} should be used as the
+     * background tint for the container, while {@code contrastColor} can be used as the foreground
+     * drawable's tint so that it is visible on the background. Essentially, tintColor should apply
+     * to the portion of the icon that borders the underlying window content (status bar's
+     * background), and the contrastColor only need be used to distinguish from the tintColor.
+     *
+     * Defaults to calling {@link #setStaticDrawableColor(int)} with only the tint color, so modern
+     * callers can just call this method and still get the default behavior.
+     */
+    default void setStaticDrawableColor(int tintColor, int contrastColor) {
+        setStaticDrawableColor(tintColor);
+    }
+
     void setDecorColor(int color);
 
     /** Sets the visible state that this displayable should be. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/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/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/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
index 50efbb5..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
@@ -167,17 +167,16 @@
         NotificationShelfViewBinderWrapperControllerImpl.unsupported
 
     override fun setShelfIcons(icons: NotificationIconContainer) {
-        if (shelfRefactor.expectEnabled()) {
-            NotificationIconContainerViewBinder.bind(
-                icons,
-                shelfIconsViewModel,
-                configurationController,
-                dozeParameters,
-                featureFlags,
-                screenOffAnimationController,
-            )
-            shelfIcons = icons
-        }
+        if (shelfRefactor.isUnexpectedlyInLegacyMode()) return
+        NotificationIconContainerViewBinder.bind(
+            icons,
+            shelfIconsViewModel,
+            configurationController,
+            dozeParameters,
+            featureFlags,
+            screenOffAnimationController,
+        )
+        shelfIcons = icons
     }
 
     override fun onDensityOrFontScaleChanged(context: Context) {
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/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9340b85..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
@@ -1901,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
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/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 79f8f22..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
@@ -2735,7 +2735,7 @@
      * @param listener callback for notification removed
      */
     public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) {
-        mShelfRefactor.assertDisabled();
+        mShelfRefactor.assertInLegacyMode();
         mOnNotificationRemovedListener = listener;
     }
 
@@ -4982,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);
@@ -5001,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 5020780..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
@@ -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/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/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 5553270..f9856b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -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/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/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/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/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/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/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 3b8e02f..22c75d8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -16,6 +16,8 @@
 
 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;
@@ -31,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;
@@ -60,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;
@@ -90,6 +94,7 @@
                 mFeatureFlags,
                 mInteractionJankMonitor,
                 deps.getKeyguardInteractor(),
+                mKeyguardTransitionInteractor,
                 mDumpManager,
                 PowerInteractorFactory.create(
                         mFakePowerRepository
@@ -105,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/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/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
deleted file mode 100644
index 712eef1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics.domain.interactor
-
-import android.hardware.biometrics.SensorLocationInternal
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.coroutines.collectLastValue
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.junit.MockitoJUnit
-
-@SmallTest
-@RunWith(JUnit4::class)
-class SideFpsOverlayInteractorTest : SysuiTestCase() {
-
-    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
-    private lateinit var testScope: TestScope
-
-    private val fingerprintRepository = FakeFingerprintPropertyRepository()
-
-    private lateinit var interactor: SideFpsOverlayInteractor
-
-    @Before
-    fun setup() {
-        testScope = TestScope(StandardTestDispatcher())
-        interactor = SideFpsOverlayInteractorImpl(fingerprintRepository)
-    }
-
-    @Test
-    fun testOverlayOffsetUpdates() =
-        testScope.runTest {
-            fingerprintRepository.setProperties(
-                sensorId = 1,
-                strength = SensorStrength.STRONG,
-                sensorType = FingerprintSensorType.REAR,
-                sensorLocations =
-                    mapOf(
-                        "" to
-                            SensorLocationInternal(
-                                "" /* displayId */,
-                                540 /* sensorLocationX */,
-                                1636 /* sensorLocationY */,
-                                130 /* sensorRadius */
-                            ),
-                        "display_id_1" to
-                            SensorLocationInternal(
-                                "display_id_1" /* displayId */,
-                                100 /* sensorLocationX */,
-                                300 /* sensorLocationY */,
-                                20 /* sensorRadius */
-                            )
-                    )
-            )
-
-            val displayId by collectLastValue(interactor.displayId)
-            val offsets by collectLastValue(interactor.overlayOffsets)
-
-            // Assert offsets of empty displayId.
-            assertThat(displayId).isEqualTo("")
-            assertThat(offsets?.displayId).isEqualTo("")
-            assertThat(offsets?.sensorLocationX).isEqualTo(540)
-            assertThat(offsets?.sensorLocationY).isEqualTo(1636)
-            assertThat(offsets?.sensorRadius).isEqualTo(130)
-
-            // Offsets should be updated correctly.
-            interactor.onDisplayChanged("display_id_1")
-            assertThat(displayId).isEqualTo("display_id_1")
-            assertThat(offsets?.displayId).isEqualTo("display_id_1")
-            assertThat(offsets?.sensorLocationX).isEqualTo(100)
-            assertThat(offsets?.sensorLocationY).isEqualTo(300)
-            assertThat(offsets?.sensorRadius).isEqualTo(20)
-
-            // Should return default offset when the displayId is invalid.
-            interactor.onDisplayChanged("invalid_display_id")
-            assertThat(displayId).isEqualTo("invalid_display_id")
-            assertThat(offsets?.displayId).isEqualTo(SensorLocationInternal.DEFAULT.displayId)
-            assertThat(offsets?.sensorLocationX)
-                .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationX)
-            assertThat(offsets?.sensorLocationY)
-                .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationY)
-            assertThat(offsets?.sensorRadius).isEqualTo(SensorLocationInternal.DEFAULT.sensorRadius)
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
new file mode 100644
index 0000000..99501c42
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.graphics.Rect
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.display.DisplayManagerGlobal
+import android.view.Display
+import android.view.DisplayInfo
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.view.WindowMetrics
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_0
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_180
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_270
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_90
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags.REST_TO_UNLOCK
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.junit.MockitoJUnit
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class SideFpsSensorInteractorTest : SysuiTestCase() {
+
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+    private lateinit var testScope: TestScope
+
+    private val fingerprintRepository = FakeFingerprintPropertyRepository()
+
+    private lateinit var underTest: SideFpsSensorInteractor
+
+    @Mock private lateinit var windowManager: WindowManager
+    @Mock private lateinit var displayStateInteractor: DisplayStateInteractor
+
+    private val contextDisplayInfo = DisplayInfo()
+    private val displayChangeEvent = MutableStateFlow(0)
+    private val currentRotation = MutableStateFlow(ROTATION_0)
+
+    @Before
+    fun setup() {
+        testScope = TestScope(StandardTestDispatcher())
+        mContext = spy(mContext)
+
+        val displayManager = mock(DisplayManagerGlobal::class.java)
+        val resources = mContext.resources
+        whenever(mContext.display)
+            .thenReturn(Display(displayManager, 1, contextDisplayInfo, resources))
+        whenever(displayStateInteractor.displayChanges).thenReturn(displayChangeEvent)
+        whenever(displayStateInteractor.currentRotation).thenReturn(currentRotation)
+
+        contextDisplayInfo.uniqueId = "current-display"
+
+        underTest =
+            SideFpsSensorInteractor(
+                mContext,
+                fingerprintRepository,
+                windowManager,
+                displayStateInteractor,
+                FakeFeatureFlagsClassic().apply { set(REST_TO_UNLOCK, true) }
+            )
+    }
+
+    @Test
+    fun testSfpsSensorAvailable() =
+        testScope.runTest {
+            val isAvailable by collectLastValue(underTest.isAvailable)
+
+            setupFingerprint(FingerprintSensorType.POWER_BUTTON)
+            assertThat(isAvailable).isTrue()
+
+            setupFingerprint(FingerprintSensorType.HOME_BUTTON)
+            assertThat(isAvailable).isFalse()
+
+            setupFingerprint(FingerprintSensorType.REAR)
+            assertThat(isAvailable).isFalse()
+
+            setupFingerprint(FingerprintSensorType.UDFPS_OPTICAL)
+            assertThat(isAvailable).isFalse()
+
+            setupFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC)
+            assertThat(isAvailable).isFalse()
+
+            setupFingerprint(FingerprintSensorType.UNKNOWN)
+            assertThat(isAvailable).isFalse()
+        }
+
+    @Test
+    fun authenticationDurationIsAvailableWhenSFPSSensorIsAvailable() =
+        testScope.runTest {
+            assertThat(collectLastValue(underTest.authenticationDuration)())
+                .isEqualTo(context.resources.getInteger(R.integer.config_restToUnlockDuration))
+        }
+
+    @Test
+    fun verticalSensorLocationIsAdjustedToScreenPositionForRotation0() =
+        testScope.runTest {
+            /*
+            (0,0)                (1000,0)
+               ------------------
+              |  ^^^^^           |  (1000, 200)
+              |   status bar    || <--- start of sensor at Rotation_0
+              |                 || <--- end of sensor
+              |                  |  (1000, 300)
+              |                  |
+               ------------------ (1000, 800)
+             */
+            setupFPLocationAndDisplaySize(
+                width = 1000,
+                height = 800,
+                rotation = ROTATION_0,
+                sensorLocationY = 200,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(1000)
+            assertThat(sensorLocation!!.top).isEqualTo(200)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    @Test
+    fun verticalSensorLocationIsAdjustedToScreenPositionForRotation270() =
+        testScope.runTest {
+            /*
+            (800,0)                     (800, 1000)
+                  ---------------------
+                 |                     | (600, 1000)
+                 |   <                || <--- end of sensor at Rotation_270
+                 |   < status bar     || <--- start of sensor
+                 |   <                 | (500, 1000)
+                 |   <                 |
+            (0,0) ---------------------
+             */
+            setupFPLocationAndDisplaySize(
+                width = 800,
+                height = 1000,
+                rotation = ROTATION_270,
+                sensorLocationY = 200,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(500)
+            assertThat(sensorLocation!!.top).isEqualTo(1000)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    @Test
+    fun verticalSensorLocationIsAdjustedToScreenPositionForRotation90() =
+        testScope.runTest {
+            /*
+                                            (0,0)
+                       ---------------------
+                      |                     | (200, 0)
+                      |               >    || <--- end of sensor at Rotation_270
+                      |    status bar >    || <--- start of sensor
+                      |               >     | (300, 0)
+                      |               >     |
+            (800,1000) ---------------------
+             */
+            setupFPLocationAndDisplaySize(
+                width = 800,
+                height = 1000,
+                rotation = ROTATION_90,
+                sensorLocationY = 200,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(200)
+            assertThat(sensorLocation!!.top).isEqualTo(0)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    @Test
+    fun verticalSensorLocationIsAdjustedToScreenPositionForRotation180() =
+        testScope.runTest {
+            /*
+
+            (1000,800) ---------------------
+                      |                     | (0, 600)
+                      |                    || <--- end of sensor at Rotation_270
+                      |    status bar      || <--- start of sensor
+                      |   \/\/\/\/\/\/\/    | (0, 500)
+                      |                     |
+                       ---------------------  (0,0)
+             */
+            setupFPLocationAndDisplaySize(
+                width = 1000,
+                height = 800,
+                rotation = ROTATION_180,
+                sensorLocationY = 200,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(0)
+            assertThat(sensorLocation!!.top).isEqualTo(500)
+        }
+
+    @Test
+    fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation0() =
+        testScope.runTest {
+            /*
+            (0,0)   (500,0)   (600,0)   (1000,0)
+               ____________===_________
+              |                        |
+              |  ^^^^^                 |
+              |   status bar           |
+              |                        |
+               ------------------------ (1000, 800)
+             */
+            setupFPLocationAndDisplaySize(
+                width = 1000,
+                height = 800,
+                rotation = ROTATION_0,
+                sensorLocationX = 500,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(500)
+            assertThat(sensorLocation!!.top).isEqualTo(0)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    @Test
+    fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation90() =
+        testScope.runTest {
+            /*
+                  (0,1000)   (0,500)   (0,400)   (0,0)
+                        ____________===_________
+                       |                        |
+                       |               >        |
+                       |   status bar  >        |
+                       |               >        |
+            (800, 1000) ------------------------
+             */
+            setupFPLocationAndDisplaySize(
+                width = 800,
+                height = 1000,
+                rotation = ROTATION_90,
+                sensorLocationX = 500,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(0)
+            assertThat(sensorLocation!!.top).isEqualTo(400)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    @Test
+    fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation180() =
+        testScope.runTest {
+            /*
+            (1000, 800)  (500, 800)   (400, 800)   (0,800)
+                       ____________===_________
+                      |                        |
+                      |                        |
+                      |   status bar           |
+                      |  \/ \/ \/ \/ \/ \/ \/  |
+                       ------------------------ (0,0)
+             */
+            setupFPLocationAndDisplaySize(
+                width = 1000,
+                height = 800,
+                rotation = ROTATION_180,
+                sensorLocationX = 500,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(400)
+            assertThat(sensorLocation!!.top).isEqualTo(800)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    @Test
+    fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation270() =
+        testScope.runTest {
+            /*
+                        (800, 500)  (800, 600)
+            (800, 0) ____________===_________ (800,1000)
+                    |  <                     |
+                    |  <                     |
+                    |  < status bar          |
+                    |  <                     |
+               (0,0) ------------------------
+             */
+            setupFPLocationAndDisplaySize(
+                width = 800,
+                height = 1000,
+                rotation = ROTATION_270,
+                sensorLocationX = 500,
+                sensorWidth = 100,
+            )
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation!!.left).isEqualTo(800)
+            assertThat(sensorLocation!!.top).isEqualTo(500)
+            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+            assertThat(sensorLocation!!.width).isEqualTo(100)
+        }
+
+    private suspend fun TestScope.setupFPLocationAndDisplaySize(
+        width: Int,
+        height: Int,
+        sensorLocationX: Int = 0,
+        sensorLocationY: Int = 0,
+        rotation: DisplayRotation,
+        sensorWidth: Int
+    ) {
+        overrideResource(R.integer.config_sfpsSensorWidth, sensorWidth)
+        setupDisplayDimensions(width, height)
+        currentRotation.value = rotation
+        setupFingerprint(x = sensorLocationX, y = sensorLocationY, displayId = "expanded_display")
+    }
+
+    private fun setupDisplayDimensions(displayWidth: Int, displayHeight: Int) {
+        whenever(windowManager.maximumWindowMetrics)
+            .thenReturn(
+                WindowMetrics(
+                    Rect(0, 0, displayWidth, displayHeight),
+                    mock(WindowInsets::class.java)
+                )
+            )
+    }
+
+    private suspend fun TestScope.setupFingerprint(
+        fingerprintSensorType: FingerprintSensorType = FingerprintSensorType.POWER_BUTTON,
+        x: Int = 0,
+        y: Int = 0,
+        displayId: String = "display_id_1"
+    ) {
+        contextDisplayInfo.uniqueId = displayId
+        fingerprintRepository.setProperties(
+            sensorId = 1,
+            strength = SensorStrength.STRONG,
+            sensorType = fingerprintSensorType,
+            sensorLocations =
+                mapOf(
+                    "someOtherDisplayId" to
+                        SensorLocationInternal(
+                            "someOtherDisplayId",
+                            x + 100,
+                            y + 100,
+                            0,
+                        ),
+                    displayId to
+                        SensorLocationInternal(
+                            displayId,
+                            x,
+                            y,
+                            0,
+                        )
+                )
+        )
+        // Emit a display change event, this happens whenever any display related change happens,
+        // rotation, active display changing etc, display switched off/on.
+        displayChangeEvent.emit(1)
+
+        runCurrent()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
index 37c70d8..2bd2bff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
@@ -43,10 +43,10 @@
     fun accessingUnspecifiedFlags_throwsException() {
         val flags: FeatureFlags = FakeFeatureFlags()
         try {
-            assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse()
+            assertThat(flags.isEnabled(Flags.NULL_FLAG)).isFalse()
             fail("Expected an exception when accessing an unspecified flag.")
         } catch (ex: IllegalStateException) {
-            assertThat(ex.message).contains("UNKNOWN(teamfood)")
+            assertThat(ex.message).contains("UNKNOWN(null_flag)")
         }
         try {
             assertThat(flags.isEnabled(unreleasedFlag)).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
index c12a581..f51745b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
@@ -22,6 +22,7 @@
 import android.content.res.Resources
 import android.content.res.Resources.NotFoundException
 import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.FakeFeatureFlagsImpl
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -65,6 +66,7 @@
     private lateinit var broadcastReceiver: BroadcastReceiver
     private lateinit var clearCacheAction: Consumer<String>
     private val serverFlagReader = ServerFlagReaderFake()
+    private val fakeGantryFlags = FakeFeatureFlagsImpl()
 
     private val teamfoodableFlagA = UnreleasedFlag(name = "a", namespace = "test", teamfood = true)
     private val teamfoodableFlagB = ReleasedFlag(name = "b", namespace = "test", teamfood = true)
@@ -72,7 +74,7 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        flagMap.put(Flags.TEAMFOOD.name, Flags.TEAMFOOD)
+        fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", false)
         flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
         flagMap.put(teamfoodableFlagB.name, teamfoodableFlagB)
         mFeatureFlagsClassicDebug =
@@ -84,6 +86,7 @@
                 resources,
                 serverFlagReader,
                 flagMap,
+                fakeGantryFlags,
                 restarter
             )
         mFeatureFlagsClassicDebug.init()
@@ -121,8 +124,6 @@
 
     @Test
     fun teamFoodFlag_False() {
-        whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any()))
-            .thenReturn(false)
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isFalse()
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue()
 
@@ -133,8 +134,7 @@
 
     @Test
     fun teamFoodFlag_True() {
-        whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any()))
-            .thenReturn(true)
+        fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", true)
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue()
 
@@ -149,8 +149,7 @@
             .thenReturn(true)
         whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.name), any()))
             .thenReturn(false)
-        whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any()))
-            .thenReturn(true)
+        fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", true)
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isFalse()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/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/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/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index e537131..4c5a214 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -54,11 +54,7 @@
 import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.statusbar.connectivity.WifiIndicators;
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository;
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository;
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor;
-import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl;
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
 import com.android.systemui.statusbar.policy.HotspotController;
@@ -111,7 +107,8 @@
 
     private WifiInteractor mWifiInteractor;
     private final TileJavaAdapter mJavaAdapter = new TileJavaAdapter();
-    private final FakeWifiRepository mWifiRepository = new FakeWifiRepository();
+    private final FakeConnectivityRepository mConnectivityRepository =
+            new FakeConnectivityRepository();
     private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     private final TestScope mTestScope = TestScopeProvider.getTestScope();
 
@@ -124,12 +121,6 @@
         mTestableLooper = TestableLooper.get(this);
 
         when(mHost.getContext()).thenReturn(mContext);
-
-        mWifiInteractor = new WifiInteractorImpl(
-                new FakeConnectivityRepository(),
-                mWifiRepository,
-                mTestScope
-        );
     }
 
     @After
@@ -204,25 +195,41 @@
     // SIGNAL_CALLBACK_DEPRECATION flag set to true
 
     @Test
-    public void stateUnavailable_wifiDisabled_newPipeline() {
+    public void stateUnavailable_noDefaultNetworks_newPipeline() {
         createAndStartTileNewImpl();
-        mWifiRepository.setIsWifiEnabled(false);
         mTestableLooper.processAllMessages();
 
         assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
     }
 
     @Test
-    public void stateUnavailable_wifiEnabled_notConnected_newPipeline() {
+    public void stateUnavailable_mobileConnected_newPipeline() {
         createAndStartTileNewImpl();
-        mWifiRepository.setIsWifiEnabled(true);
-        mWifiRepository.setWifiNetwork(Inactive.INSTANCE);
+        mConnectivityRepository.setMobileConnected(true);
         mTestableLooper.processAllMessages();
 
         assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
     }
 
     @Test
+    public void stateInactive_wifiConnected_newPipeline() {
+        createAndStartTileNewImpl();
+        mConnectivityRepository.setWifiConnected(true);
+        mTestableLooper.processAllMessages();
+
+        assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+    }
+
+    @Test
+    public void stateInactive_ethernetConnected_newPipeline() {
+        createAndStartTileNewImpl();
+        mConnectivityRepository.setEthernetConnected(true);
+        mTestableLooper.processAllMessages();
+
+        assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+    }
+
+    @Test
     public void stateActive_wifiConnectedAndCasting_newPipeline() {
         createAndStartTileNewImpl();
         CastController.CastDevice device = new CastController.CastDevice();
@@ -231,40 +238,27 @@
         devices.add(device);
         when(mController.getCastDevices()).thenReturn(devices);
 
-        mWifiRepository.setWifiNetwork(
-                new WifiNetworkModel.Active(
-                        1 /* networkId */,
-                        true /* isValidated */,
-                        3 /* level */,
-                        "test" /* ssid */,
-                        WifiNetworkModel.HotspotDeviceType.NONE,
-                        false /* isPasspointAccessPoint */,
-                        false /* isOnlineSignUpforPasspointAccessPoint */,
-                        null /* passpointProviderFriendlyName */
-                ));
+        mConnectivityRepository.setWifiConnected(true);
+
         mTestableLooper.processAllMessages();
 
         assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
     }
 
     @Test
-    public void stateInactive_wifiConnectedNotCasting_newPipeline() {
+    public void stateActive_ethernetConnectedAndCasting_newPipeline() {
         createAndStartTileNewImpl();
+        CastController.CastDevice device = new CastController.CastDevice();
+        device.state = CastDevice.STATE_CONNECTED;
+        List<CastDevice> devices = new ArrayList<>();
+        devices.add(device);
+        when(mController.getCastDevices()).thenReturn(devices);
 
-        mWifiRepository.setWifiNetwork(
-                new WifiNetworkModel.Active(
-                        1 /* networkId */,
-                        true /* isValidated */,
-                        3 /* level */,
-                        "test" /* ssid */,
-                        WifiNetworkModel.HotspotDeviceType.NONE,
-                        false /* isPasspointAccessPoint */,
-                        false /* isOnlineSignUpforPasspointAccessPoint */,
-                        null /* passpointProviderFriendlyName */
-                ));
+        mConnectivityRepository.setEthernetConnected(true);
+
         mTestableLooper.processAllMessages();
 
-        assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+        assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
     }
 
     // -------------------------------------------------
@@ -512,7 +506,7 @@
                 mNetworkController,
                 mHotspotController,
                 mDialogLaunchAnimator,
-                mWifiInteractor,
+                mConnectivityRepository,
                 mJavaAdapter,
                 mFeatureFlags
         );
@@ -555,7 +549,7 @@
                 mNetworkController,
                 mHotspotController,
                 mDialogLaunchAnimator,
-                mWifiInteractor,
+                mConnectivityRepository,
                 mJavaAdapter,
                 mFeatureFlags
         );
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 58e36be..10c7c43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -21,6 +21,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -49,6 +51,7 @@
     private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
     private var mobileIconsViewModel: MobileIconsViewModel =
         MobileIconsViewModel(
@@ -61,6 +64,7 @@
                     FakeConnectivityRepository(),
                 ),
             constants = mock(),
+            flags,
             scope = testScope.backgroundScope,
         )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 61dd69a..88a5c17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -25,6 +25,8 @@
 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
 import com.android.systemui.model.SysUiState
@@ -137,6 +139,7 @@
         )
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
     private var mobileIconsViewModel: MobileIconsViewModel =
         MobileIconsViewModel(
@@ -149,6 +152,7 @@
                     FakeConnectivityRepository(),
                 ),
             constants = mock(),
+            flags,
             scope = testScope.backgroundScope,
         )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
index 3ae1f35..da4dc85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertEquals
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -111,6 +112,15 @@
     }
 
     @Test
+    fun showDialog_singleAppIsDefault() {
+        dialog.show()
+
+        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+        val singleApp = context.getString(R.string.screen_share_permission_dialog_option_single_app)
+        assertEquals(spinner.adapter.getItem(0), singleApp)
+    }
+
+    @Test
     fun showDialog_cancelClicked_dialogIsDismissed() {
         dialog.show()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/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 d8821aa..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), 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
@@ -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 5091a70..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>()
 
@@ -87,6 +88,7 @@
             .thenReturn(false)
         whenever(userManager.isUserUnlocked).thenReturn(true)
         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 4f0cec5..6223e25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -390,6 +390,7 @@
                 mFeatureFlags,
                 mInteractionJankMonitor,
                 mKeyguardInteractor,
+                mKeyguardTransitionInteractor,
                 mDumpManager,
                 mPowerInteractor));
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 607cdab..df38f93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -4,6 +4,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
@@ -31,6 +33,7 @@
     private val sceneInteractor = utils.sceneInteractor()
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
     private var mobileIconsViewModel: MobileIconsViewModel =
         MobileIconsViewModel(
@@ -43,6 +46,7 @@
                     FakeConnectivityRepository(),
                 ),
             constants = mock(),
+            flags,
             scope = testScope.backgroundScope,
         )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index c423782..3064f4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -21,6 +21,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -55,6 +57,7 @@
         )
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
     private var mobileIconsViewModel: MobileIconsViewModel =
         MobileIconsViewModel(
@@ -67,6 +70,7 @@
                     FakeConnectivityRepository(),
                 ),
             constants = mock(),
+            flags,
             scope = testScope.backgroundScope,
         )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java
new file mode 100644
index 0000000..3a9c24a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java
@@ -0,0 +1,597 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
+import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.KeyguardManager;
+import android.app.Notification;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.FakeSettings;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationLockscreenUserManagerMainThreadTest extends SysuiTestCase {
+    @Mock
+    private NotificationPresenter mPresenter;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private UserTracker mUserTracker;
+
+    // Dependency mocks:
+    @Mock
+    private NotificationVisibilityProvider mVisibilityProvider;
+    @Mock
+    private CommonNotifCollection mNotifCollection;
+    @Mock
+    private DevicePolicyManager mDevicePolicyManager;
+    @Mock
+    private NotificationClickNotifier mClickNotifier;
+    @Mock
+    private OverviewProxyService mOverviewProxyService;
+    @Mock
+    private KeyguardManager mKeyguardManager;
+    @Mock
+    private DeviceProvisionedController mDeviceProvisionedController;
+    @Mock
+    private StatusBarStateController mStatusBarStateController;
+    @Mock
+    private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+
+    private UserInfo mCurrentUser;
+    private UserInfo mSecondaryUser;
+    private UserInfo mWorkUser;
+    private FakeSettings mSettings;
+    private TestNotificationLockscreenUserManager mLockscreenUserManager;
+    private NotificationEntry mCurrentUserNotif;
+    private NotificationEntry mSecondaryUserNotif;
+    private NotificationEntry mWorkProfileNotif;
+    private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
+    private Executor mBackgroundExecutor = Runnable::run; // Direct executor
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
+
+        int currentUserId = ActivityManager.getCurrentUser();
+        when(mUserTracker.getUserId()).thenReturn(currentUserId);
+        mSettings = new FakeSettings();
+        mSettings.setUserId(ActivityManager.getCurrentUser());
+        mCurrentUser = new UserInfo(currentUserId, "", 0);
+        mSecondaryUser = new UserInfo(currentUserId + 1, "", 0);
+        mWorkUser = new UserInfo(currentUserId + 2, "" /* name */, null /* iconPath */, 0,
+                UserManager.USER_TYPE_PROFILE_MANAGED);
+
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);
+        when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(
+                mCurrentUser, mWorkUser));
+        when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(
+                mSecondaryUser));
+        mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
+                Handler.createAsync(Looper.myLooper()));
+
+        Notification notifWithPrivateVisibility = new Notification();
+        notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE;
+        mCurrentUserNotif = new NotificationEntryBuilder()
+                .setNotification(notifWithPrivateVisibility)
+                .setUser(new UserHandle(mCurrentUser.id))
+                .build();
+        mSecondaryUserNotif = new NotificationEntryBuilder()
+                .setNotification(notifWithPrivateVisibility)
+                .setUser(new UserHandle(mSecondaryUser.id))
+                .build();
+        mWorkProfileNotif = new NotificationEntryBuilder()
+                .setNotification(notifWithPrivateVisibility)
+                .setUser(new UserHandle(mWorkUser.id))
+                .build();
+
+        mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
+        mLockscreenUserManager.setUpWithPresenter(mPresenter);
+    }
+
+    private void changeSetting(String setting) {
+        final Collection<Uri> lockScreenUris = new ArrayList<>();
+        lockScreenUris.add(Settings.Secure.getUriFor(setting));
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false,
+            lockScreenUris, 0);
+    }
+
+    @Test
+    public void testLockScreenShowNotificationsFalse() {
+        mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+        assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
+    }
+
+    @Test
+    public void testLockScreenShowNotificationsTrue() {
+        mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+        assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
+    }
+
+    @Test
+    public void testLockScreenAllowPrivateNotificationsTrue() {
+        mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testLockScreenAllowPrivateNotificationsFalse() {
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testLockScreenAllowsWorkPrivateNotificationsFalse() {
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mWorkUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
+    }
+
+    @Test
+    public void testLockScreenAllowsWorkPrivateNotificationsTrue() {
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mWorkUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
+    }
+
+    @Test
+    public void testCurrentUserPrivateNotificationsNotRedacted() {
+        // GIVEN current user doesn't allow private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN current user's notification is redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+    }
+
+    @Test
+    public void testCurrentUserPrivateNotificationsRedacted() {
+        // GIVEN current user allows private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN current user's notification isn't redacted
+        assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+    }
+
+    @Test
+    public void testWorkPrivateNotificationsRedacted() {
+        // GIVEN work profile doesn't private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mWorkUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN work profile notification is redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+        assertFalse(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
+    }
+
+    @Test
+    public void testWorkPrivateNotificationsNotRedacted() {
+        // GIVEN work profile allows private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mWorkUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN work profile notification isn't redacted
+        assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+        assertTrue(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
+    }
+
+    @Test
+    public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() {
+        // GIVEN work profile allows private notifications to show but the other users don't
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mWorkUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN the work profile notification doesn't need to be redacted
+        assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+
+        // THEN the current user and secondary user notifications do need to be redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+    }
+
+    @Test
+    public void testWorkProfileRedacted_otherUsersNotRedacted() {
+        // GIVEN work profile doesn't allow private notifications to show but the other users do
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mWorkUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mCurrentUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN the work profile notification needs to be redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+
+        // THEN the current user and secondary user notifications don't need to be redacted
+        assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+        assertFalse(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+    }
+
+    @Test
+    public void testSecondaryUserNotRedacted_currentUserRedacted() {
+        // GIVEN secondary profile allows private notifications to show but the current user
+        // doesn't allow private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN the secondary profile notification still needs to be redacted because the current
+        // user's setting takes precedence
+        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+    }
+
+    @Test
+    public void testUserSwitchedCallsOnUserSwitching() {
+        mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id,
+                mContext);
+        verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id);
+    }
+
+    @Test
+    public void testIsLockscreenPublicMode() {
+        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
+        mLockscreenUserManager.setLockscreenPublicMode(true, mCurrentUser.id);
+        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
+    }
+
+    @Test
+    public void testUpdateIsPublicMode() {
+        when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
+
+        NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
+        mLockscreenUserManager.addNotificationStateChangedListener(listener);
+        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+
+        // first call explicitly sets user 0 to not public; notifies
+        mLockscreenUserManager.updatePublicMode();
+        TestableLooper.get(this).processAllMessages();
+        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+        verify(listener).onNotificationStateChanged();
+        clearInvocations(listener);
+
+        // calling again has no changes; does not notify
+        mLockscreenUserManager.updatePublicMode();
+        TestableLooper.get(this).processAllMessages();
+        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+        verify(listener, never()).onNotificationStateChanged();
+
+        // Calling again with keyguard now showing makes user 0 public; notifies
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        mLockscreenUserManager.updatePublicMode();
+        TestableLooper.get(this).processAllMessages();
+        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+        verify(listener).onNotificationStateChanged();
+        clearInvocations(listener);
+
+        // calling again has no changes; does not notify
+        mLockscreenUserManager.updatePublicMode();
+        TestableLooper.get(this).processAllMessages();
+        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+        verify(listener, never()).onNotificationStateChanged();
+    }
+
+    @Test
+    public void testDevicePolicyDoesNotAllowNotifications() {
+        // User allows them
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides notifs on lockscreen
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testDevicePolicyDoesNotAllowNotifications_secondary() {
+        Mockito.clearInvocations(mDevicePolicyManager);
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides notifications
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
+                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Test
+    public void testDevicePolicy_noPrivateNotifications() {
+        Mockito.clearInvocations(mDevicePolicyManager);
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides sensitive content
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+    }
+
+    @Test
+    public void testDevicePolicy_noPrivateNotifications_userAll() {
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides sensitive content
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder()
+                .setNotification(new Notification())
+                .setUser(UserHandle.ALL)
+                .build()));
+    }
+
+    @Test
+    public void testDevicePolicyPrivateNotifications_secondary() {
+        Mockito.clearInvocations(mDevicePolicyManager);
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides sensitive content
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
+                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+    }
+
+    @Test
+    public void testHideNotifications_primary() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testHideNotifications_secondary() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Test
+    public void testHideNotifications_secondary_userSwitch() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Test
+    public void testShowNotifications_secondary_userSwitch() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Test
+    public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
+        // DevicePolicy allows notifications
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(0);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        // KeyguardManager does not allow notifications
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no
+        // callback, so it's only updated when the setting is
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testUserAllowsNotificationsInPublic_settingsChange() {
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+
+        // User disables
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    private class TestNotificationLockscreenUserManager
+            extends NotificationLockscreenUserManagerImpl {
+        public TestNotificationLockscreenUserManager(Context context) {
+            super(
+                    context,
+                    mBroadcastDispatcher,
+                    mDevicePolicyManager,
+                    mUserManager,
+                    mUserTracker,
+                    (() -> mVisibilityProvider),
+                    (() -> mNotifCollection),
+                    mClickNotifier,
+                    (() -> mOverviewProxyService),
+                    NotificationLockscreenUserManagerMainThreadTest.this.mKeyguardManager,
+                    mStatusBarStateController,
+                    Handler.createAsync(Looper.myLooper()),
+                    Handler.createAsync(Looper.myLooper()),
+                    mBackgroundExecutor,
+                    mDeviceProvisionedController,
+                    mKeyguardStateController,
+                    mSettings,
+                    mock(DumpManager.class),
+                    mock(LockPatternUtils.class),
+                    mFakeFeatureFlags);
+        }
+
+        public BroadcastReceiver getBaseBroadcastReceiverForTest() {
+            return mBaseBroadcastReceiver;
+        }
+
+        public UserTracker.Callback getUserTrackerCallbackForTest() {
+            return mUserChangedCallback;
+        }
+
+        public ContentObserver getLockscreenSettingsObserverForTest() {
+            return mLockscreenSettingsObserver;
+        }
+
+        public ContentObserver getSettingsObserverForTest() {
+            return mSettingsObserver;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 19863ec..a5f5fc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -16,17 +16,23 @@
 
 package com.android.systemui.statusbar;
 
+import static android.app.Notification.VISIBILITY_PRIVATE;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
 import static android.os.UserHandle.USER_ALL;
+import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
 import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
 
 import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
@@ -38,12 +44,15 @@
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
+import android.app.NotificationChannel;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -59,6 +68,8 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.settings.UserTracker;
@@ -74,12 +85,16 @@
 import com.google.android.collect.Lists;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.Executor;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -121,11 +136,15 @@
     private NotificationEntry mCurrentUserNotif;
     private NotificationEntry mSecondaryUserNotif;
     private NotificationEntry mWorkProfileNotif;
+    private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
+    private Executor mBackgroundExecutor = Runnable::run; // Direct executor
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+
         int currentUserId = ActivityManager.getCurrentUser();
         when(mUserTracker.getUserId()).thenReturn(currentUserId);
         mSettings = new FakeSettings();
@@ -138,103 +157,144 @@
         when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);
         when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(
                 mCurrentUser, mWorkUser));
+        when(mUserManager.getUsers()).thenReturn(Lists.newArrayList(
+                mCurrentUser, mWorkUser, mSecondaryUser));
         when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(
                 mSecondaryUser));
         mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
                 Handler.createAsync(Looper.myLooper()));
 
         Notification notifWithPrivateVisibility = new Notification();
-        notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE;
+        notifWithPrivateVisibility.visibility = VISIBILITY_PRIVATE;
         mCurrentUserNotif = new NotificationEntryBuilder()
                 .setNotification(notifWithPrivateVisibility)
                 .setUser(new UserHandle(mCurrentUser.id))
                 .build();
+        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+        channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE);
+        mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking())
+                .setChannel(channel)
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+        when(mNotifCollection.getEntry(mCurrentUserNotif.getKey())).thenReturn(mCurrentUserNotif);
         mSecondaryUserNotif = new NotificationEntryBuilder()
                 .setNotification(notifWithPrivateVisibility)
                 .setUser(new UserHandle(mSecondaryUser.id))
                 .build();
+        mSecondaryUserNotif.setRanking(new RankingBuilder(mSecondaryUserNotif.getRanking())
+                .setChannel(channel)
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+        when(mNotifCollection.getEntry(
+                mSecondaryUserNotif.getKey())).thenReturn(mSecondaryUserNotif);
         mWorkProfileNotif = new NotificationEntryBuilder()
                 .setNotification(notifWithPrivateVisibility)
                 .setUser(new UserHandle(mWorkUser.id))
                 .build();
+        mWorkProfileNotif.setRanking(new RankingBuilder(mWorkProfileNotif.getRanking())
+                .setChannel(channel)
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+        when(mNotifCollection.getEntry(mWorkProfileNotif.getKey())).thenReturn(mWorkProfileNotif);
 
         mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
         mLockscreenUserManager.setUpWithPresenter(mPresenter);
     }
 
+    private void changeSetting(String setting) {
+        final Collection<Uri> lockScreenUris = new ArrayList<>();
+        lockScreenUris.add(Settings.Secure.getUriFor(setting));
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false,
+            lockScreenUris, 0);
+    }
+
     @Test
     public void testLockScreenShowNotificationsFalse() {
         mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
         assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
     }
 
     @Test
     public void testLockScreenShowNotificationsTrue() {
         mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
         assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
     }
 
     @Test
     public void testLockScreenAllowPrivateNotificationsTrue() {
-        mSettings.putInt(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
         assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
     }
 
     @Test
     public void testLockScreenAllowPrivateNotificationsFalse() {
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
         assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
     }
 
     @Test
     public void testLockScreenAllowsWorkPrivateNotificationsFalse() {
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mWorkUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
         assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
     }
 
     @Test
     public void testLockScreenAllowsWorkPrivateNotificationsTrue() {
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mWorkUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
         assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
     }
 
     @Test
-    public void testCurrentUserPrivateNotificationsNotRedacted() {
+    public void testCurrentUserPrivateNotificationsRedacted() {
         // GIVEN current user doesn't allow private notifications to show
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
 
         // THEN current user's notification is redacted
         assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
     }
 
     @Test
-    public void testCurrentUserPrivateNotificationsRedacted() {
+    public void testCurrentUserPrivateNotificationsNotRedacted() {
         // GIVEN current user allows private notifications to show
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
 
         // THEN current user's notification isn't redacted
         assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
     }
 
     @Test
+    public void testCurrentUserPrivateNotificationsRedactedChannel() {
+        // GIVEN current user allows private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // but doesn't allow it at the channel level
+        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+        channel.setLockscreenVisibility(VISIBILITY_PRIVATE);
+        mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking())
+                .setChannel(channel)
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+        // THEN the notification is redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+    }
+
+    @Test
     public void testWorkPrivateNotificationsRedacted() {
         // GIVEN work profile doesn't private notifications to show
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mWorkUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
 
         // THEN work profile notification is redacted
         assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
@@ -244,9 +304,9 @@
     @Test
     public void testWorkPrivateNotificationsNotRedacted() {
         // GIVEN work profile allows private notifications to show
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mWorkUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
 
         // THEN work profile notification isn't redacted
         assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
@@ -256,13 +316,15 @@
     @Test
     public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() {
         // GIVEN work profile allows private notifications to show but the other users don't
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mWorkUser.id);
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mCurrentUser.id);
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mSecondaryUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
 
         // THEN the work profile notification doesn't need to be redacted
         assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
@@ -275,13 +337,15 @@
     @Test
     public void testWorkProfileRedacted_otherUsersNotRedacted() {
         // GIVEN work profile doesn't allow private notifications to show but the other users do
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mWorkUser.id);
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mCurrentUser.id);
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mSecondaryUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
 
         // THEN the work profile notification needs to be redacted
         assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
@@ -295,11 +359,12 @@
     public void testSecondaryUserNotRedacted_currentUserRedacted() {
         // GIVEN secondary profile allows private notifications to show but the current user
         // doesn't allow private notifications to show
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
                 mCurrentUser.id);
-        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
                 mSecondaryUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
 
         // THEN the secondary profile notification still needs to be redacted because the current
         // user's setting takes precedence
@@ -323,6 +388,7 @@
     @Test
     public void testUpdateIsPublicMode() {
         when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
 
         NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
         mLockscreenUserManager.addNotificationStateChangedListener(listener);
@@ -330,24 +396,28 @@
 
         // first call explicitly sets user 0 to not public; notifies
         mLockscreenUserManager.updatePublicMode();
+        TestableLooper.get(this).processAllMessages();
         assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
         verify(listener).onNotificationStateChanged();
         clearInvocations(listener);
 
         // calling again has no changes; does not notify
         mLockscreenUserManager.updatePublicMode();
+        TestableLooper.get(this).processAllMessages();
         assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
         verify(listener, never()).onNotificationStateChanged();
 
         // Calling again with keyguard now showing makes user 0 public; notifies
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         mLockscreenUserManager.updatePublicMode();
+        TestableLooper.get(this).processAllMessages();
         assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
         verify(listener).onNotificationStateChanged();
         clearInvocations(listener);
 
         // calling again has no changes; does not notify
         mLockscreenUserManager.updatePublicMode();
+        TestableLooper.get(this).processAllMessages();
         assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
         verify(listener, never()).onNotificationStateChanged();
     }
@@ -356,14 +426,14 @@
     public void testDevicePolicyDoesNotAllowNotifications() {
         // User allows them
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         // DevicePolicy hides notifs on lockscreen
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
 
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
         mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
         mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
                 new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
@@ -371,19 +441,18 @@
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
     }
 
-    @Ignore("b/286230167")
     @Test
     public void testDevicePolicyDoesNotAllowNotifications_userAll() {
         // User allows them
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         // DevicePolicy hides notifications
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
 
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
         mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
         mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
                 new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
@@ -392,98 +461,105 @@
     }
 
     @Test
-    @Ignore("b/286230167")
     public void testDevicePolicyDoesNotAllowNotifications_secondary() {
+        Mockito.clearInvocations(mDevicePolicyManager);
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         // DevicePolicy hides notifications
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
                 .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
 
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
         mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
         mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
                 new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
 
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
 
-        // TODO (b/286230167): enable assertion
         verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
     }
 
     @Test
     public void testDevicePolicy_noPrivateNotifications() {
+        Mockito.clearInvocations(mDevicePolicyManager);
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         // DevicePolicy hides sensitive content
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
 
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
         mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
         mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
                 new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
 
         assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
 
-        // TODO (b/286230167): enable assertion. It's currently called 4 times.
-        //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
     }
 
     @Test
     public void testDevicePolicy_noPrivateNotifications_userAll() {
+        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+        channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE);
+        NotificationEntry notifEntry = new NotificationEntryBuilder()
+                .setNotification(new Notification())
+                .setUser(UserHandle.ALL)
+                .build();
+        notifEntry.setRanking(new RankingBuilder(notifEntry.getRanking())
+                .setChannel(channel)
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+        when(mNotifCollection.getEntry(notifEntry.getKey())).thenReturn(notifEntry);
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         // DevicePolicy hides sensitive content
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
 
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
         mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
         mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
                 new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
 
-        assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder()
-                .setNotification(new Notification())
-                .setUser(UserHandle.ALL)
-                .build()));
+        assertTrue(mLockscreenUserManager.needsRedaction(notifEntry));
     }
 
     @Test
     public void testDevicePolicyPrivateNotifications_secondary() {
+        Mockito.clearInvocations(mDevicePolicyManager);
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         // DevicePolicy hides sensitive content
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
                 .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
 
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
         mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
         mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
                 new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
 
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
         assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
 
-        // TODO (b/286230167): enable assertion. It's currently called 5 times.
-        //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
     }
 
     @Test
     public void testHideNotifications_primary() {
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
     }
@@ -491,16 +567,17 @@
     @Test
     public void testHideNotifications_secondary() {
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
     }
 
-    @Ignore("b/286230167")
     @Test
     public void testHideNotifications_workProfile() {
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mWorkUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mWorkUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mWorkUser.id));
     }
@@ -508,67 +585,62 @@
     @Test
     public void testHideNotifications_secondary_userSwitch() {
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
-        TestNotificationLockscreenUserManager lockscreenUserManager
-                = new TestNotificationLockscreenUserManager(mContext);
-        lockscreenUserManager.setUpWithPresenter(mPresenter);
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
 
-        lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
-        assertFalse(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
     }
 
     @Test
     public void testShowNotifications_secondary_userSwitch() {
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
-        TestNotificationLockscreenUserManager lockscreenUserManager
-                = new TestNotificationLockscreenUserManager(mContext);
-        lockscreenUserManager.setUpWithPresenter(mPresenter);
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
 
-        lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
-        assertTrue(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
     }
 
-    @Ignore("b/286230167")
     @Test
     public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications() {
+        // KeyguardManager does not allow notifications
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
         // DevicePolicy allows notifications
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(0);
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
         mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
         mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
                 new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
 
-        // KeyguardManager does not
-        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
-
         assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
     }
 
     @Test
     public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
         // DevicePolicy allows notifications
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(0);
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
         mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
         mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
                 new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
 
-        // KeyguardManager does not
+        // KeyguardManager does not allow notifications
         when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
 
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no
+        // callback, so it's only updated when the setting is
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
     }
 
@@ -576,31 +648,30 @@
     public void testUserAllowsNotificationsInPublic_settingsChange() {
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
 
         // User disables
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
     }
 
-    @Ignore("b/286230167")
     @Test
     public void testUserAllowsNotificationsInPublic_devicePolicyChange() {
         // User allows notifications
         mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
 
         assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
 
         // DevicePolicy disables notifications
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
                 .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
-        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
-        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
         mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
         mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
                 new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
@@ -608,6 +679,28 @@
         assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
     }
 
+    @Test
+    public void testNewUserAdded() {
+        int newUserId = 14;
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, newUserId);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, newUserId);
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, newUserId))
+                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+        BroadcastReceiver broadcastReceiver =
+                mLockscreenUserManager.getBaseBroadcastReceiverForTest();
+        final Bundle extras = new Bundle();
+        final Intent intent = new Intent(Intent.ACTION_USER_ADDED);
+        intent.putExtras(extras);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
+        broadcastReceiver.onReceive(mContext, intent);
+
+        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), eq(newUserId));
+
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(newUserId));
+        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(newUserId));
+    }
+
     private class TestNotificationLockscreenUserManager
             extends NotificationLockscreenUserManagerImpl {
         public TestNotificationLockscreenUserManager(Context context) {
@@ -623,12 +716,17 @@
                     (() -> mOverviewProxyService),
                     NotificationLockscreenUserManagerTest.this.mKeyguardManager,
                     mStatusBarStateController,
-                    Handler.createAsync(Looper.myLooper()),
+                    Handler.createAsync(TestableLooper.get(
+                            NotificationLockscreenUserManagerTest.this).getLooper()),
+                    Handler.createAsync(TestableLooper.get(
+                            NotificationLockscreenUserManagerTest.this).getLooper()),
+                    mBackgroundExecutor,
                     mDeviceProvisionedController,
                     mKeyguardStateController,
                     mSettings,
                     mock(DumpManager.class),
-                    mock(LockPatternUtils.class));
+                    mock(LockPatternUtils.class),
+                    mFakeFeatureFlags);
         }
 
         public BroadcastReceiver getBaseBroadcastReceiverForTest() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index 83f2a5d..b922ab3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -22,6 +22,7 @@
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
 
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -36,6 +37,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.NotificationChannel;
 import android.content.Context;
 import android.os.Handler;
 import android.os.UserHandle;
@@ -51,6 +53,9 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -96,6 +101,7 @@
     @Mock private UserTracker mUserTracker;
     private final FakeSettings mSecureSettings = new FakeSettings();
     private final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
+    private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
 
     private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
     private NotificationEntry mEntry;
@@ -116,7 +122,8 @@
                                 mStatusBarStateController,
                                 mUserTracker,
                                 mSecureSettings,
-                                mGlobalSettings);
+                                mGlobalSettings,
+                                mFeatureFlags);
         mKeyguardNotificationVisibilityProvider = component.getProvider();
         for (CoreStartable startable : component.getCoreStartables().values()) {
             startable.start();
@@ -424,6 +431,7 @@
 
     @Test
     public void publicMode_settingsDisallow() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
         // GIVEN an 'unfiltered-keyguard-showing' state
         setupUnfilteredState(mEntry);
 
@@ -433,12 +441,59 @@
         when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))
                 .thenReturn(false);
 
+        mEntry.setRanking(new RankingBuilder()
+                .setChannel(new NotificationChannel("1", "1", 4))
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE)
+                .setKey(mEntry.getKey()).build());
+
+        // THEN filter out the entry
+        assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
+    public void publicMode_settingsDisallow_mainThread() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
+        // GIVEN an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState(mEntry);
+
+        // WHEN the notification's user is in public mode and settings are configured to disallow
+        // notifications in public mode
+        when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true);
+        when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))
+                .thenReturn(false);
+
+        mEntry.setRanking(new RankingBuilder()
+                .setChannel(new NotificationChannel("1", "1", 4))
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE)
+                .setKey(mEntry.getKey()).build());
+
         // THEN filter out the entry
         assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
     }
 
     @Test
     public void publicMode_notifDisallowed() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+        channel.setLockscreenVisibility(VISIBILITY_SECRET);
+        // GIVEN an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState(mEntry);
+
+        // WHEN the notification's user is in public mode and settings are configured to disallow
+        // notifications in public mode
+        when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true);
+        mEntry.setRanking(new RankingBuilder()
+                .setKey(mEntry.getKey())
+                .setChannel(channel)
+                .setVisibilityOverride(VISIBILITY_SECRET).build());
+
+        // THEN filter out the entry
+        assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
+    public void publicMode_notifDisallowed_mainThread() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
         // GIVEN an 'unfiltered-keyguard-showing' state
         setupUnfilteredState(mEntry);
 
@@ -506,6 +561,54 @@
     }
 
     @Test
+    public void notificationChannelVisibilityNoOverride() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+        // GIVEN a VISIBILITY_PRIVATE notification
+        NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID));
+        entryBuilder.modifyNotification(mContext)
+                .setVisibility(VISIBILITY_PRIVATE);
+        mEntry = entryBuilder.build();
+        // ranking says secret because of DPC or Setting
+        mEntry.setRanking(new RankingBuilder()
+                .setKey(mEntry.getKey())
+                .setVisibilityOverride(VISIBILITY_SECRET)
+                .setImportance(IMPORTANCE_HIGH)
+                .build());
+
+        // WHEN we're in an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState(mEntry);
+
+        // THEN don't hide the entry based on visibility. (Redaction is handled elsewhere.)
+        assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
+    public void notificationChannelVisibilitySecret() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+        // GIVEN a VISIBILITY_PRIVATE notification
+        NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID));
+        entryBuilder.modifyNotification(mContext)
+                .setVisibility(VISIBILITY_PRIVATE);
+        // And a VISIBILITY_SECRET NotificationChannel
+        NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_HIGH);
+        channel.setLockscreenVisibility(VISIBILITY_SECRET);
+        mEntry = entryBuilder.build();
+        // WHEN we're in an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState(mEntry);
+        when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true);
+        when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true);
+
+        mEntry.setRanking(new RankingBuilder(mEntry.getRanking())
+                .setChannel(channel)
+                .build());
+
+        // THEN hide the entry based on visibility.
+        assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
     public void notificationVisibilityPrivate() {
         // GIVEN a VISIBILITY_PRIVATE notification
         NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
@@ -635,7 +738,8 @@
                     @BindsInstance SysuiStatusBarStateController statusBarStateController,
                     @BindsInstance UserTracker userTracker,
                     @BindsInstance SecureSettings secureSettings,
-                    @BindsInstance GlobalSettings globalSettings
+                    @BindsInstance GlobalSettings globalSettings,
+                    @BindsInstance FeatureFlagsClassic featureFlags
             );
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/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/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/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/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index f7e0120..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,6 +15,8 @@
  */
 package com.android.systemui;
 
+import static com.android.systemui.Flags.FLAG_EXAMPLE_FLAG;
+
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
@@ -25,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;
@@ -68,6 +71,9 @@
             new AndroidXAnimatorIsolationRule();
 
     @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Rule
     public SysuiTestableContext mContext = new SysuiTestableContext(
             InstrumentationRegistry.getContext(), getLeakCheck());
     @Rule
@@ -88,6 +94,10 @@
         if (isRobolectricTest()) {
             mContext = mContext.createDefaultDisplayContext();
         }
+        // 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,
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/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/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 10ac2eb..f09cb19 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -48,3 +48,17 @@
     description: "Stops using the deprecated PackageListObserver."
     bug: "304561459"
 }
+
+flag {
+    name: "scan_packages_without_lock"
+    namespace: "accessibility"
+    description: "Scans packages for accessibility service/activity info without holding the A11yMS lock"
+    bug: "295969873"
+}
+
+flag {
+    name: "reduce_touch_exploration_sensitivity"
+    namespace: "accessibility"
+    description: "Reduces touch exploration sensitivity by only sending a hover event when the ifnger has moved the amount of pixels defined by the system's touch slop."
+    bug: "303677860"
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index aa6d800..8c1d444 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -290,14 +290,13 @@
 
     private final Set<ComponentName> mTempComponentNameSet = new HashSet<>();
 
-    private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList =
-            new ArrayList<>();
-
     private final IntArray mTempIntArray = new IntArray(0);
 
     private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients =
             new RemoteCallbackList<>();
 
+    private PackageMonitor mPackageMonitor;
+
     @VisibleForTesting
     final SparseArray<AccessibilityUserState> mUserStates = new SparseArray<>();
 
@@ -531,6 +530,19 @@
         disableAccessibilityMenuToMigrateIfNeeded();
     }
 
+    /**
+     * Returns if the current thread is holding {@link #mLock}. Used for testing
+     * deadlock bug fixes.
+     *
+     * <p><strong>Warning:</strong> this should not be used for production logic
+     * because by the time you receive an answer it may no longer be valid.
+     * </p>
+     */
+    @VisibleForTesting
+    boolean unsafeIsLockHeld() {
+        return Thread.holdsLock(mLock);
+    }
+
     @Override
     public int getCurrentUserIdLocked() {
         return mCurrentUserId;
@@ -690,6 +702,19 @@
         }
     }
 
+    private void onSomePackagesChangedLocked(
+            @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos,
+            @Nullable List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
+        final AccessibilityUserState userState = getCurrentUserStateLocked();
+        // Reload the installed services since some services may have different attributes
+        // or resolve info (does not support equals), etc. Remove them then to force reload.
+        userState.mInstalledServices.clear();
+        if (readConfigurationForUserStateLocked(userState,
+                    parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos)) {
+            onUserStateChangedLocked(userState);
+        }
+    }
+
     private void onPackageRemovedLocked(String packageName) {
         final AccessibilityUserState userState = getCurrentUserState();
         final Predicate<ComponentName> filter =
@@ -721,8 +746,13 @@
         }
     }
 
+    @VisibleForTesting
+    PackageMonitor getPackageMonitor() {
+        return mPackageMonitor;
+    }
+
     private void registerBroadcastReceivers() {
-        PackageMonitor monitor = new PackageMonitor() {
+        mPackageMonitor = new PackageMonitor() {
             @Override
             public void onSomePackagesChanged() {
                 if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
@@ -730,13 +760,25 @@
                             FLAGS_PACKAGE_BROADCAST_RECEIVER);
                 }
 
+                final int userId = getChangingUserId();
+                List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
+                List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
+                if (Flags.scanPackagesWithoutLock()) {
+                    parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+                    parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
+                }
                 synchronized (mLock) {
                     // Only the profile parent can install accessibility services.
                     // Therefore we ignore packages from linked profiles.
-                    if (getChangingUserId() != mCurrentUserId) {
+                    if (userId != mCurrentUserId) {
                         return;
                     }
-                    onSomePackagesChangedLocked();
+                    if (Flags.scanPackagesWithoutLock()) {
+                        onSomePackagesChangedLocked(parsedAccessibilityServiceInfos,
+                                parsedAccessibilityShortcutInfos);
+                    } else {
+                        onSomePackagesChangedLocked();
+                    }
                 }
             }
 
@@ -751,8 +793,14 @@
                             FLAGS_PACKAGE_BROADCAST_RECEIVER,
                             "packageName=" + packageName + ";uid=" + uid);
                 }
+                final int userId = getChangingUserId();
+                List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
+                List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
+                if (Flags.scanPackagesWithoutLock()) {
+                    parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+                    parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
+                }
                 synchronized (mLock) {
-                    final int userId = getChangingUserId();
                     if (userId != mCurrentUserId) {
                         return;
                     }
@@ -765,8 +813,13 @@
                     // Reloads the installed services info to make sure the rebound service could
                     // get a new one.
                     userState.mInstalledServices.clear();
-                    final boolean configurationChanged =
-                            readConfigurationForUserStateLocked(userState);
+                    final boolean configurationChanged;
+                    if (Flags.scanPackagesWithoutLock()) {
+                        configurationChanged = readConfigurationForUserStateLocked(userState,
+                                parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
+                    } else {
+                        configurationChanged = readConfigurationForUserStateLocked(userState);
+                    }
                     if (reboundAService || configurationChanged) {
                         onUserStateChangedLocked(userState);
                     }
@@ -839,7 +892,7 @@
         };
 
         // package changes
-        monitor.register(mContext, null,  UserHandle.ALL, true);
+        mPackageMonitor.register(mContext, null,  UserHandle.ALL, true);
 
         if (!Flags.deprecatePackageListObserver()) {
             final PackageManagerInternal pm = LocalServices.getService(
@@ -1831,8 +1884,15 @@
         mA11yWindowManager.onTouchInteractionEnd();
     }
 
-    private void switchUser(int userId) {
+    @VisibleForTesting
+    void switchUser(int userId) {
         mMagnificationController.updateUserIdIfNeeded(userId);
+        List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
+        List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
+        if (Flags.scanPackagesWithoutLock()) {
+            parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+            parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
+        }
         synchronized (mLock) {
             if (mCurrentUserId == userId && mInitialized) {
                 return;
@@ -1857,7 +1917,12 @@
             mCurrentUserId = userId;
             AccessibilityUserState userState = getCurrentUserStateLocked();
 
-            readConfigurationForUserStateLocked(userState);
+            if (Flags.scanPackagesWithoutLock()) {
+                readConfigurationForUserStateLocked(userState,
+                        parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
+            } else {
+                readConfigurationForUserStateLocked(userState);
+            }
             mSecurityPolicy.onSwitchUserLocked(mCurrentUserId, userState.mEnabledServices);
             // Even if reading did not yield change, we have to update
             // the state since the context in which the current user
@@ -2105,8 +2170,17 @@
         }
     }
 
-    private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState) {
-        mTempAccessibilityServiceInfoList.clear();
+    /**
+     * Finds packages that provide AccessibilityService interfaces, and parses
+     * their metadata XML to build up {@link AccessibilityServiceInfo} objects.
+     *
+     * <p>
+     * <strong>Note:</strong> XML parsing is a resource-heavy operation that may
+     * stall, so this method should not be called while holding a lock.
+     * </p>
+     */
+    private List<AccessibilityServiceInfo> parseAccessibilityServiceInfos(int userId) {
+        List<AccessibilityServiceInfo> result = new ArrayList<>();
 
         int flags = PackageManager.GET_SERVICES
                 | PackageManager.GET_META_DATA
@@ -2114,12 +2188,14 @@
                 | PackageManager.MATCH_DIRECT_BOOT_AWARE
                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 
-        if (userState.getBindInstantServiceAllowedLocked()) {
-            flags |= PackageManager.MATCH_INSTANT;
+        synchronized (mLock) {
+            if (getUserStateLocked(userId).getBindInstantServiceAllowedLocked()) {
+                flags |= PackageManager.MATCH_INSTANT;
+            }
         }
 
         List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser(
-                new Intent(AccessibilityService.SERVICE_INTERFACE), flags, mCurrentUserId);
+                new Intent(AccessibilityService.SERVICE_INTERFACE), flags, userId);
 
         for (int i = 0, count = installedServices.size(); i < count; i++) {
             ResolveInfo resolveInfo = installedServices.get(i);
@@ -2132,40 +2208,60 @@
             AccessibilityServiceInfo accessibilityServiceInfo;
             try {
                 accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext);
-                if (!accessibilityServiceInfo.isWithinParcelableSize()) {
-                    Slog.e(LOG_TAG, "Skipping service "
-                            + accessibilityServiceInfo.getResolveInfo().getComponentInfo()
-                            + " because service info size is larger than safe parcelable limits.");
-                    continue;
-                }
-                if (userState.mCrashedServices.contains(serviceInfo.getComponentName())) {
-                    // Restore the crashed attribute.
-                    accessibilityServiceInfo.crashed = true;
-                }
-                mTempAccessibilityServiceInfoList.add(accessibilityServiceInfo);
             } catch (XmlPullParserException | IOException xppe) {
                 Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe);
+                continue;
+            }
+            if (!accessibilityServiceInfo.isWithinParcelableSize()) {
+                Slog.e(LOG_TAG, "Skipping service "
+                        + accessibilityServiceInfo.getResolveInfo().getComponentInfo()
+                        + " because service info size is larger than safe parcelable limits.");
+                continue;
+            }
+            result.add(accessibilityServiceInfo);
+        }
+        return result;
+    }
+
+    private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState,
+            @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos) {
+        for (int i = 0, count = parsedAccessibilityServiceInfos.size(); i < count; i++) {
+            AccessibilityServiceInfo accessibilityServiceInfo =
+                    parsedAccessibilityServiceInfos.get(i);
+            if (userState.mCrashedServices.contains(accessibilityServiceInfo.getComponentName())) {
+                // Restore the crashed attribute.
+                accessibilityServiceInfo.crashed = true;
             }
         }
 
-        if (!mTempAccessibilityServiceInfoList.equals(userState.mInstalledServices)) {
+        if (!parsedAccessibilityServiceInfos.equals(userState.mInstalledServices)) {
             userState.mInstalledServices.clear();
-            userState.mInstalledServices.addAll(mTempAccessibilityServiceInfoList);
-            mTempAccessibilityServiceInfoList.clear();
+            userState.mInstalledServices.addAll(parsedAccessibilityServiceInfos);
             return true;
         }
-
-        mTempAccessibilityServiceInfoList.clear();
         return false;
     }
 
-    private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState) {
-        final List<AccessibilityShortcutInfo> shortcutInfos = AccessibilityManager
-                .getInstance(mContext).getInstalledAccessibilityShortcutListAsUser(
-                        mContext, mCurrentUserId);
-        if (!shortcutInfos.equals(userState.mInstalledShortcuts)) {
+    /**
+     * Returns the {@link AccessibilityShortcutInfo}s of the installed
+     * accessibility shortcut targets for the given user.
+     *
+     * <p>
+     * <strong>Note:</strong> XML parsing is a resource-heavy operation that may
+     * stall, so this method should not be called while holding a lock.
+     * </p>
+     */
+    private List<AccessibilityShortcutInfo> parseAccessibilityShortcutInfos(int userId) {
+        // TODO: b/297279151 - This should be implemented here, not by AccessibilityManager.
+        return AccessibilityManager.getInstance(mContext)
+                .getInstalledAccessibilityShortcutListAsUser(mContext, userId);
+    }
+
+    private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState,
+            List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
+        if (!parsedAccessibilityShortcutInfos.equals(userState.mInstalledShortcuts)) {
             userState.mInstalledShortcuts.clear();
-            userState.mInstalledShortcuts.addAll(shortcutInfos);
+            userState.mInstalledShortcuts.addAll(parsedAccessibilityShortcutInfos);
             return true;
         }
         return false;
@@ -2890,9 +2986,23 @@
         userState.setFilterKeyEventsEnabledLocked(false);
     }
 
+    // ErrorProne doesn't understand that this method is only called while locked,
+    // returning an error for accessing mCurrentUserId.
+    @SuppressWarnings("GuardedBy")
     private boolean readConfigurationForUserStateLocked(AccessibilityUserState userState) {
-        boolean somethingChanged = readInstalledAccessibilityServiceLocked(userState);
-        somethingChanged |= readInstalledAccessibilityShortcutLocked(userState);
+        return readConfigurationForUserStateLocked(userState,
+                parseAccessibilityServiceInfos(mCurrentUserId),
+                parseAccessibilityShortcutInfos(mCurrentUserId));
+    }
+
+    private boolean readConfigurationForUserStateLocked(
+            AccessibilityUserState userState,
+            List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos,
+            List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
+        boolean somethingChanged = readInstalledAccessibilityServiceLocked(
+                userState, parsedAccessibilityServiceInfos);
+        somethingChanged |= readInstalledAccessibilityShortcutLocked(
+                userState, parsedAccessibilityShortcutInfos);
         somethingChanged |= readEnabledAccessibilityServicesLocked(userState);
         somethingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState);
         somethingChanged |= readTouchExplorationEnabledSettingLocked(userState);
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index c418485..fc8d4f8 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -882,10 +882,22 @@
         final int pointerIndex = event.findPointerIndex(pointerId);
         switch (event.getPointerCount()) {
             case 1:
-            // Touch exploration.
+                // Touch exploration.
                 sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
-                mDispatcher.sendMotionEvent(
-                        event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
+                if (Flags.reduceTouchExplorationSensitivity()
+                        && mState.getLastInjectedHoverEvent() != null) {
+                    final MotionEvent lastEvent = mState.getLastInjectedHoverEvent();
+                    final float deltaX = lastEvent.getX() - rawEvent.getX();
+                    final float deltaY = lastEvent.getY() - rawEvent.getY();
+                    final double moveDelta = Math.hypot(deltaX, deltaY);
+                    if (moveDelta > mTouchSlop) {
+                        mDispatcher.sendMotionEvent(
+                                event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
+                    }
+                } else {
+                    mDispatcher.sendMotionEvent(
+                            event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
+                }
                 break;
             case 2:
                 if (mGestureDetector.isMultiFingerGesturesEnabled()
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 72242d2..e4f1d3a 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -363,6 +363,7 @@
                 case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS:
                 case AutofillFeatureFlags.DEVICE_CONFIG_PREFER_PROVIDER_OVER_PCC:
                 case AutofillFeatureFlags.DEVICE_CONFIG_PCC_USE_FALLBACK:
+                case Flags.FLAG_AUTOFILL_CREDMAN_INTEGRATION:
                     setDeviceConfigProperties();
                     break;
                 case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES:
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index f5562d2..4298c07 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -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/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 1a8dd3a..ee41a69 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -28,6 +28,7 @@
 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;
@@ -115,6 +116,7 @@
 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;
@@ -142,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;
@@ -957,14 +962,40 @@
         return mContentCaptureManagerServiceStub;
     }
 
-    /** @hide */
+    /**
+     * 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);
         }
-        return Collections.emptyList();
+        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 {
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/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/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/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/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 03b0cfc..e3aa161 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -234,7 +234,9 @@
         }
     }
 
-    @VisibleForTesting void onUserChanged(int userHandle) {
+    // should be called in handler thread (same thread that started animation)
+    @VisibleForTesting
+    void onUserChanged(int userHandle) {
         final ContentResolver cr = getContext().getContentResolver();
 
         if (mCurrentUser != UserHandle.USER_NULL) {
@@ -473,6 +475,15 @@
         }
     }
 
+    // should be called in handler thread (same thread that started animation)
+    @VisibleForTesting
+    void cancelAllAnimators() {
+        mNightDisplayTintController.cancelAnimator();
+        mGlobalSaturationTintController.cancelAnimator();
+        mReduceBrightColorsTintController.cancelAnimator();
+        mDisplayWhiteBalanceTintController.cancelAnimator();
+    }
+
     private boolean resetReduceBrightColors() {
         if (mCurrentUser == UserHandle.USER_NULL) {
             return false;
diff --git a/services/core/java/com/android/server/display/feature/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 7050c5a..fae8383 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -67,20 +67,31 @@
             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
      */
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 a85e10d..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"
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/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index b090334..27e1b9a 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -60,84 +60,99 @@
     private static final long TIMEOUT_MS = 10_000;
 
     /** Handler for registering timeouts for live entries. */
+    @GuardedBy("mLock")
     private final Handler mHandler;
 
     /** Singleton instance of the History. */
-    @GuardedBy("ImeTrackerService.this")
+    @GuardedBy("mLock")
     private final History mHistory = new History();
 
+    private final Object mLock = new Object();
+
     ImeTrackerService(@NonNull Looper looper) {
         mHandler = new Handler(looper, null /* callback */, true /* async */);
     }
 
     @NonNull
     @Override
-    public synchronized ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
+    public ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
             @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
         final var binder = new Binder();
         final var token = new ImeTracker.Token(binder, tag);
         final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_SHOW, ImeTracker.STATUS_RUN,
                 origin, reason);
-        mHistory.addEntry(binder, entry);
+        synchronized (mLock) {
+            mHistory.addEntry(binder, entry);
 
-        // Register a delayed task to handle the case where the new entry times out.
-        mHandler.postDelayed(() -> {
-            synchronized (ImeTrackerService.this) {
-                mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
-            }
-        }, TIMEOUT_MS);
-
+            // Register a delayed task to handle the case where the new entry times out.
+            mHandler.postDelayed(() -> {
+                synchronized (mLock) {
+                    mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT,
+                            ImeTracker.PHASE_NOT_SET);
+                }
+            }, TIMEOUT_MS);
+        }
         return token;
     }
 
     @NonNull
     @Override
-    public synchronized ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
+    public ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
             @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
         final var binder = new Binder();
         final var token = new ImeTracker.Token(binder, tag);
         final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_HIDE, ImeTracker.STATUS_RUN,
                 origin, reason);
-        mHistory.addEntry(binder, entry);
+        synchronized (mLock) {
+            mHistory.addEntry(binder, entry);
 
-        // Register a delayed task to handle the case where the new entry times out.
-        mHandler.postDelayed(() -> {
-            synchronized (ImeTrackerService.this) {
-                mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
-            }
-        }, TIMEOUT_MS);
-
+            // Register a delayed task to handle the case where the new entry times out.
+            mHandler.postDelayed(() -> {
+                synchronized (mLock) {
+                    mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT,
+                            ImeTracker.PHASE_NOT_SET);
+                }
+            }, TIMEOUT_MS);
+        }
         return token;
     }
 
     @Override
-    public synchronized void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
-        final var entry = mHistory.getEntry(binder);
-        if (entry == null) return;
+    public void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
+        synchronized (mLock) {
+            final var entry = mHistory.getEntry(binder);
+            if (entry == null) return;
 
-        entry.mPhase = phase;
+            entry.mPhase = phase;
+        }
     }
 
     @Override
-    public synchronized void onFailed(@NonNull ImeTracker.Token statsToken,
-            @ImeTracker.Phase int phase) {
-        mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
+    public void onFailed(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
+        synchronized (mLock) {
+            mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
+        }
     }
 
     @Override
-    public synchronized void onCancelled(@NonNull ImeTracker.Token statsToken,
-            @ImeTracker.Phase int phase) {
-        mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
+    public void onCancelled(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
+        synchronized (mLock) {
+            mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
+        }
     }
 
     @Override
-    public synchronized void onShown(@NonNull ImeTracker.Token statsToken) {
-        mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+    public void onShown(@NonNull ImeTracker.Token statsToken) {
+        synchronized (mLock) {
+            mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+        }
     }
 
     @Override
-    public synchronized void onHidden(@NonNull ImeTracker.Token statsToken) {
-        mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+    public void onHidden(@NonNull ImeTracker.Token statsToken) {
+        synchronized (mLock) {
+            mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+        }
     }
 
     /**
@@ -146,25 +161,30 @@
      * @param statsToken the token corresponding to the current IME request.
      * @param requestWindowName the name of the window that created the IME request.
      */
-    public synchronized void onImmsUpdate(@NonNull ImeTracker.Token statsToken,
+    public void onImmsUpdate(@NonNull ImeTracker.Token statsToken,
             @NonNull String requestWindowName) {
-        final var entry = mHistory.getEntry(statsToken.getBinder());
-        if (entry == null) return;
+        synchronized (mLock) {
+            final var entry = mHistory.getEntry(statsToken.getBinder());
+            if (entry == null) return;
 
-        entry.mRequestWindowName = requestWindowName;
+            entry.mRequestWindowName = requestWindowName;
+        }
     }
 
     /** Dumps the contents of the history. */
-    public synchronized void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-        mHistory.dump(pw, prefix);
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        synchronized (mLock) {
+            mHistory.dump(pw, prefix);
+        }
     }
 
     @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
     @Override
-    public synchronized boolean hasPendingImeVisibilityRequests() {
+    public boolean hasPendingImeVisibilityRequests() {
         super.hasPendingImeVisibilityRequests_enforcePermission();
-
-        return !mHistory.mLiveEntries.isEmpty();
+        synchronized (mLock) {
+            return !mHistory.mLiveEntries.isEmpty();
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 1c5ecb7..1e8b387 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -45,7 +45,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
diff --git a/services/core/java/com/android/server/media/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/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/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/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index edb45aa..2967818 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1269,10 +1269,9 @@
                     replace = true;
                     if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing package: " + pkgName);
                 }
-                final AndroidPackage oldPackage = mPm.mPackages.get(pkgName);
-                if (replace && oldPackage != null) {
+                if (replace) {
                     // Prevent apps opting out from runtime permissions
-                    final int oldTargetSdk = oldPackage.getTargetSdkVersion();
+                    final int oldTargetSdk = ps.getTargetSdkVersion();
                     final int newTargetSdk = parsedPackage.getTargetSdkVersion();
                     if (oldTargetSdk > Build.VERSION_CODES.LOLLIPOP_MR1
                             && newTargetSdk <= Build.VERSION_CODES.LOLLIPOP_MR1) {
@@ -1284,10 +1283,10 @@
                                         + " target SDK " + oldTargetSdk + " does.");
                     }
                     // Prevent persistent apps from being updated
-                    if (oldPackage.isPersistent()
+                    if (ps.isPersistent()
                             && ((installFlags & PackageManager.INSTALL_STAGED) == 0)) {
                         throw new PrepareFailure(PackageManager.INSTALL_FAILED_INVALID_APK,
-                                "Package " + oldPackage.getPackageName() + " is a persistent app. "
+                                "Package " + pkgName + " is a persistent app. "
                                         + "Persistent apps are not updateable.");
                     }
                 }
@@ -1668,7 +1667,7 @@
                     }
 
                     // don't allow a system upgrade unless the upgrade hash matches
-                    if (oldPackage != null && oldPackage.getRestrictUpdateHash() != null
+                    if (oldPackageState.getRestrictUpdateHash() != null
                             && oldPackageState.isSystem()) {
                         final byte[] digestBytes;
                         try {
@@ -1684,12 +1683,13 @@
                             throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,
                                     "Could not compute hash: " + pkgName11);
                         }
-                        if (!Arrays.equals(oldPackage.getRestrictUpdateHash(), digestBytes)) {
+                        if (!Arrays.equals(oldPackageState.getRestrictUpdateHash(), digestBytes)) {
                             throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,
                                     "New package fails restrict-update check: " + pkgName11);
                         }
                         // retain upgrade restriction
-                        parsedPackage.setRestrictUpdateHash(oldPackage.getRestrictUpdateHash());
+                        parsedPackage.setRestrictUpdateHash(
+                                oldPackageState.getRestrictUpdateHash());
                     }
 
                     if (oldPackage != null) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 68aa93d..abeabc9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1469,8 +1469,7 @@
             archPkg.versionCodeMajor = (int) (longVersionCode >> 32);
             archPkg.versionCode = (int) longVersionCode;
 
-            // TODO(b/297916136): extract target sdk version.
-            archPkg.targetSdkVersion = MIN_INSTALLABLE_TARGET_SDK;
+            archPkg.targetSdkVersion = ps.getTargetSdkVersion();
 
             // These get translated in flags important for user data management.
             archPkg.defaultToDeviceProtectedStorage = String.valueOf(
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 2e60064..9f4e86d 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -99,6 +99,7 @@
         private static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE = 1 << 1;
         private static final int UPDATE_AVAILABLE = 1 << 2;
         private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 3;
+        private static final int PERSISTENT = 1 << 4;
     }
     private int mBooleans;
 
@@ -217,6 +218,11 @@
     @Nullable
     private String mAppMetadataFilePath;
 
+    private int mTargetSdkVersion;
+
+    @Nullable
+    private byte[] mRestrictUpdateHash;
+
     /**
      * Snapshot support.
      */
@@ -535,6 +541,24 @@
         onChanged();
     }
 
+    public PackageSetting setIsPersistent(boolean isPersistent) {
+        setBoolean(Booleans.PERSISTENT, isPersistent);
+        onChanged();
+        return this;
+    }
+
+    public PackageSetting setTargetSdkVersion(int targetSdkVersion) {
+        mTargetSdkVersion = targetSdkVersion;
+        onChanged();
+        return this;
+    }
+
+    public PackageSetting setRestrictUpdateHash(byte[] restrictUpdateHash) {
+        mRestrictUpdateHash = restrictUpdateHash;
+        onChanged();
+        return this;
+    }
+
     @Override
     public int getSharedUserAppId() {
         return mSharedUserAppId;
@@ -701,6 +725,9 @@
         categoryOverride = other.categoryOverride;
         mDomainSetId = other.mDomainSetId;
         mAppMetadataFilePath = other.mAppMetadataFilePath;
+        mTargetSdkVersion = other.mTargetSdkVersion;
+        mRestrictUpdateHash = other.mRestrictUpdateHash == null
+                ? null : other.mRestrictUpdateHash.clone();
 
         usesSdkLibraries = other.usesSdkLibraries != null
                 ? Arrays.copyOf(other.usesSdkLibraries,
@@ -1572,6 +1599,11 @@
         return getBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE);
     }
 
+    @Override
+    public boolean isPersistent() {
+        return getBoolean(Booleans.PERSISTENT);
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -1714,11 +1746,21 @@
         return mAppMetadataFilePath;
     }
 
+    @DataClass.Generated.Member
+    public int getTargetSdkVersion() {
+        return mTargetSdkVersion;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable byte[] getRestrictUpdateHash() {
+        return mRestrictUpdateHash;
+    }
+
     @DataClass.Generated(
-            time = 1694196905013L,
+            time = 1696979728639L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
-            inputSignatures = "private  int mBooleans\nprivate  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate  long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic  com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic  com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(long,int)\n  void setDeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static  void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isIncremental()\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic  com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic  com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic  com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final  int INSTALL_PERMISSION_FIXED\nprivate static final  int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+            inputSignatures = "private  int mBooleans\nprivate  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate  long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate  int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic  com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic  com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  void setSharedUserAppId(int)\npublic  com.android.server.pm.PackageSetting setIsPersistent(boolean)\npublic  com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic  com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  boolean isRequestLegacyExternalStorage()\npublic  boolean isUserDataFragile()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n  int[] queryInstalledUsers(int[],boolean)\n  int[] queryUsersInstalledOrHasData(int[])\n  long getCeDataInode(int)\n  long getDeDataInode(int)\n  void setCeDataInode(long,int)\n  void setDeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static  void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isIncremental()\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic  com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic  com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic  com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final  int INSTALL_PERMISSION_FIXED\nprivate static final  int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int PERSISTENT\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 0cac790..8d8acfd4 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -220,7 +220,8 @@
                     UserManagerService.getInstance(), usesSdkLibraries,
                     parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
                     parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
-                    newDomainSetId);
+                    newDomainSetId, parsedPackage.isPersistent(),
+                    parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
         } else {
             // make a deep copy to avoid modifying any existing system state.
             pkgSetting = new PackageSetting(pkgSetting);
@@ -240,7 +241,8 @@
                     UserManagerService.getInstance(),
                     usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(),
                     usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
-                    parsedPackage.getMimeGroups(), newDomainSetId);
+                    parsedPackage.getMimeGroups(), newDomainSetId, parsedPackage.isPersistent(),
+                    parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
         }
 
         if (createNewPackage && originalPkgSetting != null) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 6e3b538..451b3a5 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -1060,7 +1060,8 @@
             boolean virtualPreload, boolean isStoppedSystemApp, UserManagerService userManager,
             String[] usesSdkLibraries, long[] usesSdkLibrariesVersions,
             String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
-            Set<String> mimeGroupNames, @NonNull UUID domainSetId) {
+            Set<String> mimeGroupNames, @NonNull UUID domainSetId, boolean isPersistent,
+            int targetSdkVersion, byte[] restrictUpdatedHash) {
         final PackageSetting pkgSetting;
         if (originalPkg != null) {
             if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "
@@ -1080,7 +1081,10 @@
                     .setUsesStaticLibrariesVersions(usesStaticLibrariesVersions)
                     // Update new package state.
                     .setLastModifiedTime(codePath.lastModified())
-                    .setDomainSetId(domainSetId);
+                    .setDomainSetId(domainSetId)
+                    .setIsPersistent(isPersistent)
+                    .setTargetSdkVersion(targetSdkVersion)
+                    .setRestrictUpdateHash(restrictUpdatedHash);
             pkgSetting.setFlags(pkgFlags)
                     .setPrivateFlags(pkgPrivateFlags);
         } else {
@@ -1092,7 +1096,10 @@
                     null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
                     0 /*sharedUserAppId*/, usesSdkLibraries, usesSdkLibrariesVersions,
                     usesStaticLibraries, usesStaticLibrariesVersions,
-                    createMimeGroups(mimeGroupNames), domainSetId);
+                    createMimeGroups(mimeGroupNames), domainSetId)
+                    .setIsPersistent(isPersistent)
+                    .setTargetSdkVersion(targetSdkVersion)
+                    .setRestrictUpdateHash(restrictUpdatedHash);
             pkgSetting.setLastModifiedTime(codePath.lastModified());
             if (sharedUser != null) {
                 pkgSetting.setSharedUserAppId(sharedUser.mAppId);
@@ -1206,7 +1213,8 @@
             int pkgPrivateFlags, @NonNull UserManagerService userManager,
             @Nullable String[] usesSdkLibraries, @Nullable long[] usesSdkLibrariesVersions,
             @Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions,
-            @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId)
+            @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId, boolean isPersistent,
+            int targetSdkVersion, byte[] restrictUpdatedHash)
                     throws PackageManagerException {
         final String pkgName = pkgSetting.getPackageName();
         if (sharedUser != null) {
@@ -1258,7 +1266,10 @@
         pkgSetting.setPrimaryCpuAbi(primaryCpuAbi)
                 .setSecondaryCpuAbi(secondaryCpuAbi)
                 .updateMimeGroups(mimeGroupNames)
-                .setDomainSetId(domainSetId);
+                .setDomainSetId(domainSetId)
+                .setIsPersistent(isPersistent)
+                .setTargetSdkVersion(targetSdkVersion)
+                .setRestrictUpdateHash(restrictUpdatedHash);
         // Update SDK library dependencies if needed.
         if (usesSdkLibraries != null && usesSdkLibrariesVersions != null
                 && usesSdkLibraries.length == usesSdkLibrariesVersions.length) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 0e98158..7331bc1 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -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()
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 3f347e4..e7137bb 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -434,4 +434,26 @@
      */
     @Nullable
     String getApexModuleName();
+
+    /**
+     * @see ApplicationInfo#FLAG_PERSISTENT
+     * @see R.styleable#AndroidManifestApplication_persistent
+     * @hide
+     */
+    boolean isPersistent();
+
+    /**
+     * @see ApplicationInfo#targetSdkVersion
+     * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion
+     * @hide
+     */
+    int getTargetSdkVersion();
+
+    /**
+     * @see R.styleable#AndroidManifestRestrictUpdate
+     * @hide
+     */
+    @Immutable.Ignore
+    @Nullable
+    byte[] getRestrictUpdateHash();
 }
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/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/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index c9db343..0656a6a 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -174,8 +174,6 @@
     private CallStateHelper mCallStateHelper;
     private KeyguardManager mKeyguardManager;
 
-    private SafetyCenterManager mSafetyCenterManager;
-
     private int mCurrentUser = USER_NULL;
 
     public SensorPrivacyService(Context context) {
@@ -191,7 +189,6 @@
         mTelephonyManager = context.getSystemService(TelephonyManager.class);
         mPackageManagerInternal = getLocalService(PackageManagerInternal.class);
         mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl();
-        mSafetyCenterManager = mContext.getSystemService(SafetyCenterManager.class);
     }
 
     @Override
@@ -656,7 +653,9 @@
             String contentTitle = getUiContext().getString(messageRes);
             Spanned contentText = Html.fromHtml(getUiContext().getString(
                     R.string.sensor_privacy_start_use_notification_content_text, packageLabel), 0);
-            String action = mSafetyCenterManager.isSafetyCenterEnabled()
+            SafetyCenterManager safetyCenterManager =
+                    mContext.getSystemService(SafetyCenterManager.class);
+            String action = safetyCenterManager.isSafetyCenterEnabled()
                     ? Settings.ACTION_PRIVACY_CONTROLS : Settings.ACTION_PRIVACY_SETTINGS;
 
             PendingIntent contentIntent = PendingIntent.getActivity(mContext, sensor,
diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
index bfe34049e..9a9b836 100644
--- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -41,8 +41,8 @@
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.SystemService;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 
 import java.io.ByteArrayInputStream;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c866dd0..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() {
@@ -8264,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();
@@ -8272,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();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 12e1e2c..777b5cd 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2832,17 +2832,22 @@
     static class OpaqueActivityHelper implements Predicate<ActivityRecord> {
         private ActivityRecord mStarting;
         private boolean mIncludeInvisibleAndFinishing;
+        private boolean mIgnoringKeyguard;
 
-        ActivityRecord getOpaqueActivity(@NonNull WindowContainer<?> container) {
+        ActivityRecord getOpaqueActivity(
+                @NonNull WindowContainer<?> container, boolean ignoringKeyguard) {
             mIncludeInvisibleAndFinishing = true;
+            mIgnoringKeyguard = ignoringKeyguard;
             return container.getActivity(this,
                     true /* traverseTopToBottom */, null /* boundary */);
         }
 
-        ActivityRecord getVisibleOpaqueActivity(@NonNull WindowContainer<?> container,
-                @Nullable ActivityRecord starting) {
+        ActivityRecord getVisibleOpaqueActivity(
+                @NonNull WindowContainer<?> container, @Nullable ActivityRecord starting,
+                boolean ignoringKeyguard) {
             mStarting = starting;
             mIncludeInvisibleAndFinishing = false;
+            mIgnoringKeyguard = ignoringKeyguard;
             final ActivityRecord opaque = container.getActivity(this,
                     true /* traverseTopToBottom */, null /* boundary */);
             mStarting = null;
@@ -2851,7 +2856,9 @@
 
         @Override
         public boolean test(ActivityRecord r) {
-            if (!mIncludeInvisibleAndFinishing && !r.visibleIgnoringKeyguard && r != mStarting) {
+            if (!mIncludeInvisibleAndFinishing && r != mStarting
+                    && ((mIgnoringKeyguard && !r.visibleIgnoringKeyguard)
+                    || (!mIgnoringKeyguard && !r.isVisible()))) {
                 // Ignore invisible activities that are not the currently starting activity
                 // (about to be visible).
                 return false;
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/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 df26b10..f51bf7f 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -54,7 +54,6 @@
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
-
 /**
  * Container for grouping WindowContainer below DisplayContent.
  *
@@ -786,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/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/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/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..016b0ff 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -199,6 +199,7 @@
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.AppTimeTracker;
 import com.android.server.uri.NeededUriGrants;
+import com.android.window.flags.Flags;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -2858,7 +2859,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;
@@ -3306,7 +3308,8 @@
         // Once at the root task level, we want to check {@link #isTranslucent(ActivityRecord)}.
         // If true, we want to get the Dimmer from the level above since we don't want to animate
         // the dim with the Task.
-        if (!isRootTask() || isTranslucent(null)) {
+        if (!isRootTask() || (Flags.dimmerRefactor() && isTranslucentAndVisible())
+                || isTranslucent(null)) {
             return super.getDimmer();
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 50bc825..7c5bc6e 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;
@@ -969,7 +989,7 @@
         // A TaskFragment isn't translucent if it has at least one visible activity that occludes
         // this TaskFragment.
         return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this,
-                starting) == null;
+                starting, true /* ignoringKeyguard */) == null;
     }
 
     /**
@@ -982,7 +1002,20 @@
             return true;
         }
         // Including finishing Activity if the TaskFragment is becoming invisible in the transition.
-        return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this) == null;
+        return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this,
+                true /* ignoringKeyguard */) == null;
+    }
+
+    /**
+     * Like {@link  #isTranslucent(ActivityRecord)} but evaluating the actual visibility of the
+     * windows rather than their visibility ignoring keyguard.
+     */
+    boolean isTranslucentAndVisible() {
+        if (!isAttached() || isForceHidden() || isForceTranslucent()) {
+            return true;
+        }
+        return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this, null,
+                false /* ignoringKeyguard */) == null;
     }
 
     ActivityRecord getTopNonFinishingActivity() {
@@ -2929,14 +2962,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 f3fb7c4..93db1ca 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -418,8 +418,8 @@
             if (transientRoot == null) continue;
             final WindowContainer<?> rootParent = transientRoot.getParent();
             if (rootParent == null || rootParent.getTopChild() == transientRoot) continue;
-            final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor
-                    .mOpaqueActivityHelper.getOpaqueActivity(rootParent);
+            final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor.mOpaqueActivityHelper
+                    .getOpaqueActivity(rootParent, true /* ignoringKeyguard */);
             if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) {
                 occludedCount++;
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f339d24..9663f3a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2261,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);
@@ -2273,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();
@@ -5299,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 726d4d7..7f36aec 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1189,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)
@@ -2756,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);
                 }
@@ -5205,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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 49af89b..5a620a3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -486,7 +486,6 @@
 import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
-import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
@@ -494,6 +493,7 @@
 import com.android.server.devicepolicy.flags.FlagUtils;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 import com.android.server.pm.DefaultCrossProfileIntentFilter;
 import com.android.server.pm.DefaultCrossProfileIntentFiltersUtils;
 import com.android.server.pm.PackageManagerLocal;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 59f1edc..c26aee8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -161,6 +161,7 @@
 import com.android.server.os.DeviceIdentifiersPolicyService;
 import com.android.server.os.NativeTombstoneManagerService;
 import com.android.server.os.SchedulingPolicyService;
+import com.android.server.pdb.PersistentDataBlockService;
 import com.android.server.people.PeopleService;
 import com.android.server.permission.access.AccessCheckingService;
 import com.android.server.pm.ApexManager;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 2889c74..952cfc4 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -24,7 +24,9 @@
 import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_UNSUSPEND;
 import static android.content.pm.parsing.FrameworkParsingPackageUtils.parsePublicKey;
 import static android.content.res.Resources.ID_NULL;
+
 import static com.android.server.pm.PackageManagerService.WRITE_USER_PACKAGE_RESTRICTIONS;
+
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
@@ -1065,7 +1067,10 @@
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID(),
+                false /*isPersistent*/,
+                34 /*targetSdkVersion*/,
+                null /*restrictUpdateHash*/);
         assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
         assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
         assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
@@ -1103,7 +1108,10 @@
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID(),
+                false /*isPersistent*/,
+                34 /*targetSdkVersion*/,
+                null /*restrictUpdateHash*/);
         assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
         assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
         assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
@@ -1143,7 +1151,10 @@
                     null /*usesStaticLibraries*/,
                     null /*usesStaticLibrariesVersions*/,
                     null /*mimeGroups*/,
-                    UUID.randomUUID());
+                    UUID.randomUUID(),
+                    false /*isPersistent*/,
+                    34 /*targetSdkVersion*/,
+                    null /*restrictUpdateHash*/);
             fail("Expected a PackageManagerException");
         } catch (PackageManagerException expected) {
         }
@@ -1179,7 +1190,10 @@
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID(),
+                false /*isPersistent*/,
+                34 /*targetSdkVersion*/,
+                null /*restrictUpdateHash*/);
         assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
         assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
         assertThat(testPkgSetting01.getFlags(), is(ApplicationInfo.FLAG_SYSTEM));
@@ -1224,7 +1238,10 @@
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID(),
+                false /*isPersistent*/,
+                34 /*targetSdkVersion*/,
+                null /*restrictUpdateHash*/);
         assertThat(testPkgSetting01.getAppId(), is(0));
         assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
         assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
@@ -1269,7 +1286,10 @@
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID(),
+                false /*isPersistent*/,
+                34 /*targetSdkVersion*/,
+                null /*restrictUpdateHash*/);
         assertThat(testPkgSetting01.getAppId(), is(10064));
         assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
         assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
@@ -1315,7 +1335,10 @@
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID(),
+                false /*isPersistent*/,
+                34 /*targetSdkVersion*/,
+                null /*restrictUpdateHash*/);
         assertThat(testPkgSetting01.getAppId(), is(10064));
         assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
         assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
@@ -1358,7 +1381,10 @@
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID(),
+                false /*isPersistent*/,
+                34 /*targetSdkVersion*/,
+                null /*restrictUpdateHash*/);
         assertThat(testPkgSetting01.getAppId(), is(0));
         assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
         assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
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/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 9ac0062..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;
@@ -699,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();
@@ -1003,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();
@@ -1012,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);
@@ -1043,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();
@@ -1070,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();
@@ -1095,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();
@@ -1118,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();
@@ -1145,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/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/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/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 5b51963..21e3b34 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -62,8 +62,6 @@
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE;
-import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED;
-import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED;
@@ -75,7 +73,6 @@
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WINDOW;
-import static com.android.server.alarm.AlarmManagerService.Constants.KEY_EXACT_ALARM_DENY_LIST;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_DEVICE_IDLE_FUZZ;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INTERVAL;
@@ -85,7 +82,6 @@
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_WINDOW;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_PRIORITY_ALARM_DELAY;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_TEMPORARY_QUOTA_BUMP;
-import static com.android.server.alarm.AlarmManagerService.Constants.MAX_EXACT_ALARM_DENY_LIST_SIZE;
 import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX;
 import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY;
 import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK;
@@ -799,47 +795,6 @@
     }
 
     @Test
-    public void updatingExactAlarmDenyList() {
-        ArraySet<String> denyListed = new ArraySet<>(new String[]{
-                "com.example.package1",
-                "com.example.package2",
-                "com.example.package3",
-        });
-        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST,
-                "com.example.package1,com.example.package2,com.example.package3");
-        assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST);
-
-
-        denyListed = new ArraySet<>(new String[]{
-                "com.example.package1",
-                "com.example.package4",
-        });
-        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST,
-                "com.example.package1,com.example.package4");
-        assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST);
-
-        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "");
-        assertEquals(0, mService.mConstants.EXACT_ALARM_DENY_LIST.size());
-    }
-
-    @Test
-    public void exactAlarmDenyListMaxSize() {
-        final ArraySet<String> expectedSet = new ArraySet<>();
-        final StringBuilder sb = new StringBuilder("package1");
-        expectedSet.add("package1");
-        for (int i = 2; i <= 2 * MAX_EXACT_ALARM_DENY_LIST_SIZE; i++) {
-            sb.append(",package");
-            sb.append(i);
-            if (i <= MAX_EXACT_ALARM_DENY_LIST_SIZE) {
-                expectedSet.add("package" + i);
-            }
-        }
-        assertEquals(MAX_EXACT_ALARM_DENY_LIST_SIZE, expectedSet.size());
-        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, sb.toString());
-        assertEquals(expectedSet, mService.mConstants.EXACT_ALARM_DENY_LIST);
-    }
-
-    @Test
     public void positiveWhileIdleQuotas() {
         setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, -3);
         assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA);
@@ -2212,50 +2167,30 @@
     }
 
     @Test
-    public void hasScheduleExactAlarmBinderCallNotDenyListedPreT() throws RemoteException {
+    public void hasScheduleExactAlarmBinderCallPreT() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT);
+        mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT);
         assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_IGNORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_IGNORED);
         assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
     }
 
     @Test
-    public void hasScheduleExactAlarmBinderCallDenyListedPreT() throws RemoteException {
-        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
-        mockScheduleExactAlarmStatePreT(true, true, MODE_ERRORED);
-        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
-        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
-        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
-        mockScheduleExactAlarmStatePreT(true, true, MODE_IGNORED);
-        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
-        mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED);
-        assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-    }
-
-    @Test
     public void hasScheduleExactAlarmBinderCallNotDeclaredPreT() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmStatePreT(false, false, MODE_DEFAULT);
+        mockScheduleExactAlarmStatePreT(false, MODE_DEFAULT);
         assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
 
-        mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED);
-        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
-        mockScheduleExactAlarmStatePreT(false, true, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(false, MODE_ALLOWED);
         assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
     }
 
@@ -2281,41 +2216,33 @@
         mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
 
         // No permission, no exemption.
-        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
-        assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
-
-        // No permission, no exemption.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         // Policy permission only, no exemption.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         mockUseExactAlarmState(true);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         mockUseExactAlarmState(false);
 
         // User permission only, no exemption.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT);
-        assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
-
-        // User permission only, no exemption.
-        mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         // No permission, exemption.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(true);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         // No permission, exemption.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(false);
         doReturn(true).when(() -> UserHandle.isCore(TEST_CALLING_UID));
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
 
         // Both permissions and exemption.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         mockUseExactAlarmState(true);
         assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
     }
@@ -2514,17 +2441,12 @@
         assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
     }
 
-    private void mockScheduleExactAlarmStatePreT(boolean declared, boolean denyList, int mode) {
+    private void mockScheduleExactAlarmStatePreT(boolean declared, int mode) {
         String[] requesters = declared ? new String[]{TEST_CALLING_PACKAGE} : EmptyArray.STRING;
         when(mPermissionManagerInternal.getAppOpPermissionPackages(SCHEDULE_EXACT_ALARM))
                 .thenReturn(requesters);
         mService.refreshExactAlarmCandidates();
 
-        if (denyList) {
-            setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, TEST_CALLING_PACKAGE);
-        } else {
-            setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "");
-        }
         when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID,
                 TEST_CALLING_PACKAGE)).thenReturn(mode);
     }
@@ -2556,7 +2478,7 @@
     public void alarmClockBinderCallWithoutPermission() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
 
         final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2630,7 +2552,7 @@
     public void exactBinderCallWithAllowlist() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
         // If permission is denied, only then allowlist will be checked.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
 
         final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2650,7 +2572,7 @@
     public void exactAllowWhileIdleBinderCallWithSEAPermission() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         final PendingIntent alarmPi = getNewMockPendingIntent();
         mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
                 FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
@@ -2676,7 +2598,7 @@
         mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
 
         mockUseExactAlarmState(true);
-        mockScheduleExactAlarmStatePreT(false, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(false, MODE_ERRORED);
         final PendingIntent alarmPi = getNewMockPendingIntent();
         mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
                 FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
@@ -2700,7 +2622,7 @@
     public void exactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
         // If permission is denied, only then allowlist will be checked.
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
 
         final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2726,7 +2648,7 @@
     public void exactBinderCallsWithoutPermissionWithoutAllowlist() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false);
 
         final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2836,7 +2758,7 @@
     public void binderCallWithUserAllowlist() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
         when(mAppStateTracker.isUidPowerSaveUserExempt(TEST_CALLING_UID)).thenReturn(true);
 
@@ -3052,135 +2974,11 @@
     }
 
     @Test
-    public void denyListChanged() {
-        mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"});
-        when(mActivityManagerInternal.getStartedUserIds()).thenReturn(EmptyArray.INT);
-
-        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2,p4,p5");
-
-        final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-        verify(mService.mHandler, times(2)).sendMessageAtTime(messageCaptor.capture(),
-                anyLong());
-
-        final List<Message> messages = messageCaptor.getAllValues();
-        for (final Message msg : messages) {
-            assertTrue("Unwanted message sent to handler: " + msg.what,
-                    msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_ADDED
-                            || msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED);
-            mService.mHandler.handleMessage(msg);
-        }
-
-        ArraySet<String> added = new ArraySet<>(new String[]{"p4", "p5"});
-        verify(mService).handleChangesToExactAlarmDenyList(eq(added), eq(true));
-
-        ArraySet<String> removed = new ArraySet<>(new String[]{"p1", "p3"});
-        verify(mService).handleChangesToExactAlarmDenyList(eq(removed), eq(false));
-    }
-
-    @Test
-    public void permissionGrantedDueToDenyList() {
-        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
-        final String[] packages = {"example.package.1", "example.package.2"};
-
-        final int appId1 = 232;
-        final int appId2 = 431;
-
-        final int userId1 = 42;
-        final int userId2 = 53;
-
-        registerAppIds(packages, new Integer[]{appId1, appId2});
-
-        when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2});
-
-        when(mPermissionManagerInternal.getAppOpPermissionPackages(
-                SCHEDULE_EXACT_ALARM)).thenReturn(packages);
-        mService.refreshExactAlarmCandidates();
-
-        final long allowListDuration = 53442;
-        when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(
-                allowListDuration);
-
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED);
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT);
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED);
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED);
-
-        mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), false);
-
-        // No permission revoked.
-        verify(mService, never()).removeExactAlarmsOnPermissionRevoked(anyInt(), anyString(),
-                anyBoolean());
-
-        // Permission got granted only for (appId1, userId2).
-        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
-        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
-        final ArgumentCaptor<UserHandle> userCaptor = ArgumentCaptor.forClass(UserHandle.class);
-
-        verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), userCaptor.capture(),
-                isNull(), bundleCaptor.capture());
-
-        assertEquals(userId2, userCaptor.getValue().getIdentifier());
-
-        // Validate the intent.
-        assertEquals(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
-                intentCaptor.getValue().getAction());
-        assertEquals(packages[0], intentCaptor.getValue().getPackage());
-
-        // Validate the options.
-        final BroadcastOptions bOptions = new BroadcastOptions(bundleCaptor.getValue());
-        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
-                bOptions.getTemporaryAppAllowlistType());
-        assertEquals(allowListDuration, bOptions.getTemporaryAppAllowlistDuration());
-        assertEquals(PowerExemptionManager.REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
-                bOptions.getTemporaryAppAllowlistReasonCode());
-    }
-
-    @Test
-    public void permissionRevokedDueToDenyList() {
-        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
-        final String[] packages = {"example.package.1", "example.package.2"};
-
-        final int appId1 = 232;
-        final int appId2 = 431;
-
-        final int userId1 = 42;
-        final int userId2 = 53;
-
-        registerAppIds(packages, new Integer[]{appId1, appId2});
-
-        when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2});
-
-        when(mPermissionManagerInternal.getAppOpPermissionPackages(
-                SCHEDULE_EXACT_ALARM)).thenReturn(packages);
-        mService.refreshExactAlarmCandidates();
-
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED);
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT);
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED);
-        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED);
-
-        mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), true);
-
-        // Permission got revoked only for (appId1, userId2)
-        verify(mService, never()).removeExactAlarmsOnPermissionRevoked(
-                eq(UserHandle.getUid(userId1, appId1)), eq(packages[0]), eq(true));
-        verify(mService, never()).removeExactAlarmsOnPermissionRevoked(
-                eq(UserHandle.getUid(userId1, appId2)), eq(packages[1]), eq(true));
-        verify(mService, never()).removeExactAlarmsOnPermissionRevoked(
-                eq(UserHandle.getUid(userId2, appId2)), eq(packages[1]), eq(true));
-
-        verify(mService).removeExactAlarmsOnPermissionRevoked(
-                eq(UserHandle.getUid(userId2, appId1)), eq(packages[0]), eq(true));
-    }
-
-    @Test
     public void opChangedPermissionRevoked() throws Exception {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
         mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
 
         mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
         assertAndHandleMessageSync(REMOVE_EXACT_ALARMS);
@@ -3193,20 +2991,7 @@
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
 
         mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
-
-        mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
-
-        verify(mService.mHandler, never()).sendMessageAtTime(
-                argThat(m -> m.what == REMOVE_EXACT_ALARMS), anyLong());
-    }
-
-    @Test
-    public void opChangedNoPermissionChangeDueToDenyList() throws Exception {
-        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
-
-        mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
-        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
 
         mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
 
@@ -3222,7 +3007,7 @@
         when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs);
 
         mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
 
         final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -3482,7 +3267,7 @@
                 .putExtra(Intent.EXTRA_REPLACING, true);
 
         mockUseExactAlarmState(false);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
         assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
 
@@ -3490,7 +3275,7 @@
         assertEquals(5, mService.mAlarmStore.size());
 
         mockUseExactAlarmState(true);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
         assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
 
@@ -3498,7 +3283,7 @@
         assertEquals(5, mService.mAlarmStore.size());
 
         mockUseExactAlarmState(false);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
         assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
 
@@ -3653,16 +3438,16 @@
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
         mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false);
 
-        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
+        mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT);
+        assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+
+        mockScheduleExactAlarmStatePreT(false, MODE_ALLOWED);
         assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
 
-        mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
 
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
-        assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
-
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
     }
 
@@ -3671,11 +3456,11 @@
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
 
         mockScheduleExactAlarmState(true);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
         assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
 
         mockScheduleExactAlarmState(false);
-        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
         assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
     }
 
diff --git a/services/tests/powerstatstests/src/com/android/server/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/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 49f22ec..cf315a4 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -33,6 +33,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
@@ -53,12 +54,18 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Icon;
 import android.hardware.display.DisplayManagerGlobal;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.LocaleList;
 import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
 import android.testing.TestableContext;
 import android.view.Display;
@@ -93,8 +100,13 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * APCT tests for {@link AccessibilityManagerService}.
@@ -104,6 +116,10 @@
     public final A11yTestableContext mTestableContext = new A11yTestableContext(
             ApplicationProvider.getApplicationContext());
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final int ACTION_ID = 20;
     private static final String LABEL = "label";
     private static final String INTENT_ACTION = "TESTACTION";
@@ -204,6 +220,8 @@
                 mA11yms.getCurrentUserIdLocked());
         when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo);
         mMockResolveInfo.serviceInfo = mock(ServiceInfo.class);
+        mMockResolveInfo.serviceInfo.packageName = "packageName";
+        mMockResolveInfo.serviceInfo.name = "className";
         mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class);
 
         when(mMockBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient);
@@ -581,6 +599,73 @@
                 ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
     }
 
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
+    // Test old behavior to validate lock detection for the old (locked access) case.
+    public void testPackageMonitorScanPackages_scansWhileHoldingLock() {
+        setupAccessibilityServiceConnection(0);
+        final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
+        when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+                .thenReturn(List.of(mMockResolveInfo));
+        when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
+
+        final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED);
+        packageIntent.setData(Uri.parse("test://package"));
+        packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked());
+        packageIntent.putExtra(Intent.EXTRA_REPLACING, true);
+        mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent);
+
+        assertThat(lockState.get()).containsExactly(true);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
+    public void testPackageMonitorScanPackages_scansWithoutHoldingLock() {
+        setupAccessibilityServiceConnection(0);
+        final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
+        when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+                .thenReturn(List.of(mMockResolveInfo));
+        when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
+
+        final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED);
+        packageIntent.setData(Uri.parse("test://package"));
+        packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked());
+        packageIntent.putExtra(Intent.EXTRA_REPLACING, true);
+        mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent);
+
+        assertThat(lockState.get()).containsExactly(false);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
+    public void testSwitchUserScanPackages_scansWithoutHoldingLock() {
+        setupAccessibilityServiceConnection(0);
+        final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
+        when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+                .thenReturn(List.of(mMockResolveInfo));
+        when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
+
+        mA11yms.switchUser(mA11yms.getCurrentUserIdLocked() + 1);
+
+        assertThat(lockState.get()).containsExactly(false);
+    }
+
+    // Single package intents can trigger multiple PackageMonitor callbacks.
+    // Collect the state of the lock in a set, since tests only care if calls
+    // were all locked or all unlocked.
+    private AtomicReference<Set<Boolean>> collectLockStateWhilePackageScanning() {
+        final AtomicReference<Set<Boolean>> lockState =
+                new AtomicReference<>(new HashSet<Boolean>());
+        doAnswer((Answer<XmlResourceParser>) invocation -> {
+            lockState.updateAndGet(set -> {
+                set.add(mA11yms.unsafeIsLockHeld());
+                return set;
+            });
+            return null;
+        }).when(mMockResolveInfo.serviceInfo).loadXmlMetaData(any(), any());
+        return lockState;
+    }
+
     private void mockManageAccessibilityGranted(TestableContext context) {
         context.getTestablePermissions().setPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
                 PackageManager.PERMISSION_GRANTED);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 1cd61e9..efcdbd4 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -44,6 +44,10 @@
 import android.graphics.PointF;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.testing.DexmakerShareClassLoaderRule;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -56,6 +60,7 @@
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.accessibility.EventStreamTransformation;
+import com.android.server.accessibility.Flags;
 import com.android.server.accessibility.utils.GestureLogParser;
 import com.android.server.testutils.OffsettableClock;
 
@@ -76,6 +81,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
+
 @RunWith(AndroidJUnit4.class)
 public class TouchExplorerTest {
 
@@ -119,6 +125,9 @@
     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
             new DexmakerShareClassLoaderRule();
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     /**
      * {@link TouchExplorer#sendDownForAllNotInjectedPointers} injecting events with the same object
      * is resulting {@link ArgumentCaptor} to capture events with last state. Before implementation
@@ -161,11 +170,16 @@
         goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
         // Wait for transiting to touch exploring state.
         mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
-        moveEachPointers(mLastEvent, p(10, 10));
-        send(mLastEvent);
+        assertState(STATE_TOUCH_EXPLORING);
+        // Manually construct the next move event. Using moveEachPointers() will batch the move
+        // event which produces zero movement for some reason.
+        float[] x = new float[1];
+        float[] y = new float[1];
+        x[0] = mLastEvent.getX(0) + mTouchSlop;
+        y[0] = mLastEvent.getY(0) + mTouchSlop;
+        send(manyPointerEvent(ACTION_MOVE, x, y));
         goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER);
         assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
-        assertState(STATE_TOUCH_EXPLORING);
     }
 
     /**
@@ -173,7 +187,8 @@
      * change the coordinates.
      */
     @Test
-    public void testOneFingerMoveWithExtraMoveEvents() {
+    @RequiresFlagsEnabled(Flags.FLAG_REDUCE_TOUCH_EXPLORATION_SENSITIVITY)
+    public void testOneFingerMoveWithExtraMoveEvents_generatesOneMoveEvent() {
         goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
         // Inject a set of move events that have the same coordinates as the down event.
         moveEachPointers(mLastEvent, p(0, 0));
@@ -181,7 +196,33 @@
         // Wait for transition to touch exploring state.
         mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
         // Now move for real.
-        moveEachPointers(mLastEvent, p(10, 10));
+        moveAtLeastTouchSlop(mLastEvent);
+        send(mLastEvent);
+        // One more move event with no change.
+        moveEachPointers(mLastEvent, p(0, 0));
+        send(mLastEvent);
+        goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER);
+        assertCapturedEvents(
+                ACTION_HOVER_ENTER,
+                ACTION_HOVER_MOVE,
+                ACTION_HOVER_EXIT);
+    }
+
+    /**
+     * Test the case where ACTION_DOWN is followed by a number of ACTION_MOVE events that do not
+     * change the coordinates.
+     */
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REDUCE_TOUCH_EXPLORATION_SENSITIVITY)
+    public void testOneFingerMoveWithExtraMoveEvents_generatesThreeMoveEvent() {
+        goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
+        // Inject a set of move events that have the same coordinates as the down event.
+        moveEachPointers(mLastEvent, p(0, 0));
+        send(mLastEvent);
+        // Wait for transition to touch exploring state.
+        mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
+        // Now move for real.
+        moveAtLeastTouchSlop(mLastEvent);
         send(mLastEvent);
         // One more move event with no change.
         moveEachPointers(mLastEvent, p(0, 0));
@@ -242,7 +283,7 @@
         moveEachPointers(mLastEvent, p(0, 0), p(0, 0));
         send(mLastEvent);
         // Now move for real.
-        moveEachPointers(mLastEvent, p(10, 10), p(10, 10));
+        moveEachPointers(mLastEvent, p(mTouchSlop, mTouchSlop), p(mTouchSlop, mTouchSlop));
         send(mLastEvent);
         goToStateClearFrom(STATE_DRAGGING_2FINGERS);
         assertCapturedEvents(ACTION_DOWN, ACTION_MOVE, ACTION_MOVE, ACTION_MOVE, ACTION_UP);
@@ -251,7 +292,7 @@
     @Test
     public void testUpEvent_OneFingerMove_clearStateAndInjectHoverEvents() {
         goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
-        moveEachPointers(mLastEvent, p(10, 10));
+        moveAtLeastTouchSlop(mLastEvent);
         send(mLastEvent);
         // Wait 10 ms to make sure that hover enter and exit are not scheduled for the same moment.
         mHandler.fastForward(10);
@@ -277,7 +318,7 @@
 
         // Wait for the finger moving to the second view.
         mHandler.fastForward(oneThirdUserIntentTimeout);
-        moveEachPointers(mLastEvent, p(10, 10));
+        moveAtLeastTouchSlop(mLastEvent);
         send(mLastEvent);
 
         // Wait for the finger lifting from the second view.
@@ -402,7 +443,6 @@
         // Manually construct the next move event. Using moveEachPointers() will batch the move
         // event onto the pointer up event which will mean that the move event still has a pointer
         // count of 3.
-        // Todo: refactor to avoid using batching as there is no special reason to do it that way.
         float[] x = new float[2];
         float[] y = new float[2];
         x[0] = mLastEvent.getX(0) + 100;
@@ -734,6 +774,9 @@
         }
     }
 
+    private void moveAtLeastTouchSlop(MotionEvent event) {
+        moveEachPointers(event, p(2 * mTouchSlop, 0));
+    }
     /**
      * A {@link android.os.Handler} that doesn't process messages until {@link #fastForward(int)} is
      * invoked.
diff --git a/services/tests/servicestests/src/com/android/server/am/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/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index 67b70684..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;
@@ -437,23 +444,81 @@
     }
 
     @Test
-    public void parseContentProtectionGroupsConfig_null() {
+    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_empty() {
+    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_notEmpty() {
+    public void parseContentProtectionGroupsConfig_disabled_notEmpty() {
+        mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
         ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
         assertThat(service.parseContentProtectionGroupsConfig("a")).isEmpty();
     }
 
+    @Test
+    public void parseContentProtectionGroupsConfig_enabled_null() {
+        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig(null)).isEmpty();
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_enabled_empty() {
+        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("")).isEmpty();
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_enabled_singleValue() {
+        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("a"))
+                .isEqualTo(List.of(List.of("a")));
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_enabled_multipleValues() {
+        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("a,b"))
+                .isEqualTo(List.of(List.of("a", "b")));
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_enabled_multipleGroups() {
+        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("a,b;c;d,e"))
+                .isEqualTo(List.of(List.of("a", "b"), List.of("c"), List.of("d", "e")));
+    }
+
+    @Test
+    public void parseContentProtectionGroupsConfig_enabled_emptyValues() {
+        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+        assertThat(service.parseContentProtectionGroupsConfig("a;b;;;c;,d,,e,;,;"))
+                .isEqualTo(List.of(List.of("a"), List.of("b"), List.of("c"), List.of("d", "e")));
+    }
+
     private class TestContentCaptureManagerService extends ContentCaptureManagerService {
 
         TestContentCaptureManagerService() {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 9b32a80..de3cfbf 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -54,8 +54,8 @@
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 import com.android.server.pm.PackageManagerLocal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 16fdfb1..76aa40c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -77,7 +77,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.AlarmManagerInternal;
-import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.pm.PackageManagerLocal;
 import com.android.server.pm.UserManagerInternal;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
index a029db9..fa3c7a4c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -22,7 +22,7 @@
 
 import android.content.Context;
 
-import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 23f14f8..02b86db 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -53,8 +53,8 @@
 
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/media/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/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/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/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/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/test-mock/api/current.txt b/test-mock/api/current.txt
index 241e691..f61cce6 100644
--- a/test-mock/api/current.txt
+++ b/test-mock/api/current.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android.test.mock {
 
   @Deprecated public class MockAccountManager {
@@ -202,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);
@@ -224,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);
@@ -241,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);
@@ -265,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);
@@ -283,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 fa2fbd2..0c800b6 100644
--- a/test-mock/api/removed.txt
+++ b/test-mock/api/removed.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android.test.mock {
 
   public class MockContext extends android.content.Context {
@@ -11,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 2b5132e..f350957 100644
--- a/test-mock/api/system-current.txt
+++ b/test-mock/api/system-current.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android.test.mock {
 
   public class MockContext extends android.content.Context {
@@ -11,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/system-removed.txt b/test-mock/api/system-removed.txt
index 14191eb..d802177 100644
--- a/test-mock/api/system-removed.txt
+++ b/test-mock/api/system-removed.txt
@@ -1,3 +1 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt
index 1752edc..9ed0108 100644
--- a/test-mock/api/test-current.txt
+++ b/test-mock/api/test-current.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android.test.mock {
 
   public class MockContext extends android.content.Context {
@@ -8,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/test-mock/api/test-removed.txt b/test-mock/api/test-removed.txt
index 14191eb..d802177 100644
--- a/test-mock/api/test-removed.txt
+++ b/test-mock/api/test-removed.txt
@@ -1,3 +1 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
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/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/test-tiny-framework/run-test-manually.sh b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
index 722905f..6bf074b 100755
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
@@ -16,14 +16,8 @@
 
 source "${0%/*}"/../../common.sh
 
-#**********************************************************************************************
-#This script is broken because it relies on soong intermediate files, which seem to have moved.
-#**********************************************************************************************
-
 # 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
@@ -61,7 +55,7 @@
 
 test_compile_classpaths=(
   $SOONG_INT/external/junit/junit/android_common/combined/junit.jar
-  $ANDROID_BUILD_TOP/out/target/common/obj/JAVA_LIBRARIES/truth-prebuilt_intermediates/classes.jar
+  $ANDROID_HOST_OUT/framework/truth-prebuilt.jar
 )
 
 test_runtime_classpaths=(
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 2e9cf42..8bc88de 100755
--- a/tools/hoststubgen/scripts/run-all-tests.sh
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -34,8 +34,7 @@
 
 run ./hoststubgen/test-framework/run-test-without-atest.sh
 
-#This script is broken because it relies on soong intermediate files, which seem to have moved.
-#run ./hoststubgen/test-tiny-framework/run-test-manually.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)