Merge "Add wear specific layout for GrantCredentialsPermissionActivity"
diff --git a/apct-tests/perftests/packagemanager/Android.bp b/apct-tests/perftests/packagemanager/Android.bp
index e84aea1..b6ea54d 100644
--- a/apct-tests/perftests/packagemanager/Android.bp
+++ b/apct-tests/perftests/packagemanager/Android.bp
@@ -34,6 +34,55 @@
     test_suites: ["device-tests"],
 
     data: [
+        ":QueriesAll4",
+        ":QueriesAll31",
+        ":QueriesAll43",
+        ":QueriesAll15",
+        ":QueriesAll27",
+        ":QueriesAll39",
+        ":QueriesAll11",
+        ":QueriesAll23",
+        ":QueriesAll35",
+        ":QueriesAll47",
+        ":QueriesAll9",
+        ":QueriesAll19",
+        ":QueriesAll1",
+        ":QueriesAll5",
+        ":QueriesAll40",
+        ":QueriesAll20",
+        ":QueriesAll32",
+        ":QueriesAll48",
+        ":QueriesAll16",
+        ":QueriesAll28",
+        ":QueriesAll44",
+        ":QueriesAll12",
+        ":QueriesAll24",
+        ":QueriesAll36",
+        ":QueriesAll6",
+        ":QueriesAll2",
+        ":QueriesAll41",
+        ":QueriesAll21",
+        ":QueriesAll37",
+        ":QueriesAll49",
+        ":QueriesAll17",
+        ":QueriesAll29",
+        ":QueriesAll33",
+        ":QueriesAll45",
+        ":QueriesAll13",
+        ":QueriesAll25",
+        ":QueriesAll7",
+        ":QueriesAll3",
+        ":QueriesAll30",
+        ":QueriesAll42",
+        ":QueriesAll10",
+        ":QueriesAll26",
+        ":QueriesAll38",
+        ":QueriesAll18",
+        ":QueriesAll22",
+        ":QueriesAll34",
+        ":QueriesAll46",
+        ":QueriesAll14",
+        ":QueriesAll8",
         ":QueriesAll0",
         ":perfetto_artifacts",
     ],
diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
index c956bf5..64b2423 100644
--- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
@@ -16,6 +16,7 @@
 
 package com.android.server.job;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
@@ -59,6 +60,19 @@
     void reportAppUsage(String packageName, int userId);
 
     /**
+     * @return {@code true} if the given notification is associated with any user-initiated jobs.
+     */
+    boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId,
+            int userId, @NonNull String packageName);
+
+    /**
+     * @return {@code true} if the given notification channel is associated with any user-initiated
+     * jobs.
+     */
+    boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel,
+            int userId, String packageName);
+
+    /**
      * Report a snapshot of sync-related jobs back to the sync manager
      */
     JobStorePersistStats getPersistStats();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 4477e94..8bd3d127 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1925,6 +1925,20 @@
         return null;
     }
 
+    @GuardedBy("mLock")
+    boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId, int userId,
+            String packageName) {
+        return mNotificationCoordinator.isNotificationAssociatedWithAnyUserInitiatedJobs(
+                notificationId, userId, packageName);
+    }
+
+    @GuardedBy("mLock")
+    boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel,
+            int userId, String packageName) {
+        return mNotificationCoordinator.isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
+                notificationChannel, userId, packageName);
+    }
+
     @NonNull
     private JobServiceContext createNewJobServiceContext() {
         return mInjector.createJobServiceContext(mService, this, mNotificationCoordinator,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
index 5a12142..f6e00ec 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
@@ -31,6 +31,7 @@
 import android.util.SparseSetArray;
 
 import com.android.server.LocalServices;
+import com.android.server.job.controllers.JobStatus;
 import com.android.server.notification.NotificationManagerInternal;
 
 class JobNotificationCoordinator {
@@ -52,16 +53,18 @@
         @NonNull
         public final UserPackage userPackage;
         public final int notificationId;
+        public final String notificationChannel;
         public final int appPid;
         public final int appUid;
         @JobService.JobEndNotificationPolicy
         public final int jobEndNotificationPolicy;
 
         NotificationDetails(@NonNull UserPackage userPackage, int appPid, int appUid,
-                int notificationId,
+                int notificationId, String notificationChannel,
                 @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) {
             this.userPackage = userPackage;
             this.notificationId = notificationId;
+            this.notificationChannel = notificationChannel;
             this.appPid = appPid;
             this.appUid = appUid;
             this.jobEndNotificationPolicy = jobEndNotificationPolicy;
@@ -84,14 +87,14 @@
             removeNotificationAssociation(hostingContext, JobParameters.STOP_REASON_UNDEFINED);
         }
         final int userId = UserHandle.getUserId(callingUid);
-        // TODO(260848384): ensure apps can't cancel the notification for user-initiated job
-        //       eg., by calling NotificationManager.cancel/All or deleting the notification channel
-        mNotificationManagerInternal.enqueueNotification(
-                packageName, packageName, callingUid, callingPid, /* tag */ null,
-                notificationId, notification, userId);
+        final JobStatus jobStatus = hostingContext.getRunningJobLocked();
+        if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) {
+            notification.flags |= Notification.FLAG_USER_INITIATED_JOB;
+        }
         final UserPackage userPackage = UserPackage.of(userId, packageName);
         final NotificationDetails details = new NotificationDetails(
-                userPackage, callingPid, callingUid, notificationId, jobEndNotificationPolicy);
+                userPackage, callingPid, callingUid, notificationId, notification.getChannelId(),
+                jobEndNotificationPolicy);
         SparseSetArray<JobServiceContext> appNotifications = mCurrentAssociations.get(userPackage);
         if (appNotifications == null) {
             appNotifications = new SparseSetArray<>();
@@ -99,6 +102,11 @@
         }
         appNotifications.add(notificationId, hostingContext);
         mNotificationDetails.put(hostingContext, details);
+        // Call into NotificationManager after internal data structures have been updated since
+        // NotificationManager calls into this class to check for any existing associations.
+        mNotificationManagerInternal.enqueueNotification(
+                packageName, packageName, callingUid, callingPid, /* tag */ null,
+                notificationId, notification, userId);
     }
 
     void removeNotificationAssociation(@NonNull JobServiceContext hostingContext,
@@ -113,6 +121,9 @@
             Slog.wtf(TAG, "Association data structures not in sync");
             return;
         }
+        final String packageName = details.userPackage.packageName;
+        final int userId = UserHandle.getUserId(details.appUid);
+        boolean stripUijFlag = true;
         ArraySet<JobServiceContext> associatedContexts = associations.get(details.notificationId);
         if (associatedContexts == null || associatedContexts.isEmpty()) {
             // No more jobs using this notification. Apply the final job stop policy.
@@ -120,12 +131,63 @@
             // so the user doesn't get confused about the app state.
             if (details.jobEndNotificationPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE
                     || stopReason == JobParameters.STOP_REASON_USER) {
-                final String packageName = details.userPackage.packageName;
                 mNotificationManagerInternal.cancelNotification(
                         packageName, packageName, details.appUid, details.appPid, /* tag */ null,
-                        details.notificationId, UserHandle.getUserId(details.appUid));
+                        details.notificationId, userId);
+                stripUijFlag = false;
+            }
+        } else {
+            // Strip the UIJ flag only if there are no other UIJs associated with the notification
+            stripUijFlag = !isNotificationAssociatedWithAnyUserInitiatedJobs(
+                    details.notificationId, userId, packageName);
+        }
+        if (stripUijFlag) {
+            // Strip the user-initiated job flag from the notification.
+            mNotificationManagerInternal.removeUserInitiatedJobFlagFromNotification(
+                    packageName, details.notificationId, userId);
+        }
+    }
+
+    boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId,
+            int userId, String packageName) {
+        final UserPackage pkgDetails = UserPackage.of(userId, packageName);
+        final SparseSetArray<JobServiceContext> associations = mCurrentAssociations.get(pkgDetails);
+        if (associations == null) {
+            return false;
+        }
+        final ArraySet<JobServiceContext> associatedContexts = associations.get(notificationId);
+        if (associatedContexts == null) {
+            return false;
+        }
+
+        // Check if any UIJs associated with this package are using the same notification
+        for (int i = associatedContexts.size() - 1; i >= 0; i--) {
+            final JobStatus jobStatus = associatedContexts.valueAt(i).getRunningJobLocked();
+            if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) {
+                return true;
             }
         }
+        return false;
+    }
+
+    boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel,
+            int userId, String packageName) {
+        for (int i = mNotificationDetails.size() - 1; i >= 0; i--) {
+            final JobServiceContext jsc = mNotificationDetails.keyAt(i);
+            final NotificationDetails details = mNotificationDetails.get(jsc);
+            // Check if the details for the given notification match and if the associated job
+            // was started as a user initiated job
+            if (details != null
+                    && UserHandle.getUserId(details.appUid) == userId
+                    && details.userPackage.packageName.equals(packageName)
+                    && details.notificationChannel.equals(notificationChannel)) {
+                final JobStatus jobStatus = jsc.getRunningJobLocked();
+                if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) {
+                    return true;
+                }
+            }
+        }
+        return false;
     }
 
     private void validateNotification(@NonNull String packageName, int callingUid,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 644d92c..aef9dd0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -3712,6 +3712,30 @@
         }
 
         @Override
+        public boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId,
+                int userId, String packageName) {
+            if (packageName == null) {
+                return false;
+            }
+            synchronized (mLock) {
+                return mConcurrencyManager.isNotificationAssociatedWithAnyUserInitiatedJobs(
+                        notificationId, userId, packageName);
+            }
+        }
+
+        @Override
+        public boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
+                String notificationChannel, int userId, String packageName) {
+            if (packageName == null || notificationChannel == null) {
+                return false;
+            }
+            synchronized (mLock) {
+                return mConcurrencyManager.isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
+                        notificationChannel, userId, packageName);
+            }
+        }
+
+        @Override
         public JobStorePersistStats getPersistStats() {
             synchronized (mLock) {
                 return new JobStorePersistStats(mJobs.getPersistStats());
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index c4e8b0e..a8b6c0b 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -105,6 +105,7 @@
 static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays";
 static const char CLOCK_ENABLED_PROP_NAME[] = "persist.sys.bootanim.clock.enabled";
 static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1;
+static const int MAX_CHECK_EXIT_INTERVAL_US = 50000;
 static constexpr size_t TEXT_POS_LEN_MAX = 16;
 static const int DYNAMIC_COLOR_COUNT = 4;
 static const char U_TEXTURE[] = "uTexture";
@@ -1678,7 +1679,17 @@
                 checkExit();
             }
 
-            usleep(part.pause * ns2us(frameDuration));
+            int pauseDuration = part.pause * ns2us(frameDuration);
+            while(pauseDuration > 0 && !exitPending()){
+                if (pauseDuration > MAX_CHECK_EXIT_INTERVAL_US) {
+                    usleep(MAX_CHECK_EXIT_INTERVAL_US);
+                    pauseDuration -= MAX_CHECK_EXIT_INTERVAL_US;
+                } else {
+                    usleep(pauseDuration);
+                    break;
+                }
+                checkExit();
+            }
 
             if (exitPending() && !part.count && mCurrentInset >= mTargetInset &&
                 !part.hasFadingPhase()) {
diff --git a/core/api/current.txt b/core/api/current.txt
index 7c832b3..12d55f4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -377,7 +377,7 @@
   public static final class R.attr {
     ctor public R.attr();
     field public static final int absListViewStyle = 16842858; // 0x101006a
-    field public static final int accessibilityDataSensitive;
+    field public static final int accessibilityDataSensitive = 16844407; // 0x1010677
     field public static final int accessibilityEventTypes = 16843648; // 0x1010380
     field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
     field public static final int accessibilityFlags = 16843652; // 0x1010384
@@ -445,12 +445,12 @@
     field public static final int allowGameFpsOverride = 16844378; // 0x101065a
     field public static final int allowNativeHeapPointerTagging = 16844306; // 0x1010612
     field public static final int allowParallelSyncs = 16843570; // 0x1010332
-    field public static final int allowSharedIsolatedProcess;
+    field public static final int allowSharedIsolatedProcess = 16844413; // 0x101067d
     field public static final int allowSingleTap = 16843353; // 0x1010259
     field public static final int allowTaskReparenting = 16843268; // 0x1010204
     field public static final int allowUndo = 16843999; // 0x10104df
     field public static final int allowUntrustedActivityEmbedding = 16844393; // 0x1010669
-    field public static final int allowUpdateOwnership;
+    field public static final int allowUpdateOwnership = 16844416; // 0x1010680
     field public static final int alpha = 16843551; // 0x101031f
     field public static final int alphabeticModifiers = 16844110; // 0x101054e
     field public static final int alphabeticShortcut = 16843235; // 0x10101e3
@@ -556,7 +556,7 @@
     field public static final int canTakeScreenshot = 16844303; // 0x101060f
     field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230
     field public static final int cantSaveState = 16844142; // 0x101056e
-    field public static final int capability;
+    field public static final int capability = 16844423; // 0x1010687
     field @Deprecated public static final int capitalize = 16843113; // 0x1010169
     field public static final int category = 16843752; // 0x10103e8
     field public static final int centerBright = 16842956; // 0x10100cc
@@ -741,7 +741,7 @@
     field public static final int ellipsize = 16842923; // 0x10100ab
     field public static final int ems = 16843096; // 0x1010158
     field public static final int enableOnBackInvokedCallback = 16844396; // 0x101066c
-    field public static final int enableTextStylingShortcuts;
+    field public static final int enableTextStylingShortcuts = 16844408; // 0x1010678
     field public static final int enableVrMode = 16844069; // 0x1010525
     field public static final int enabled = 16842766; // 0x101000e
     field public static final int end = 16843996; // 0x10104dc
@@ -810,7 +810,7 @@
     field public static final int focusableInTouchMode = 16842971; // 0x10100db
     field public static final int focusedByDefault = 16844100; // 0x1010544
     field @Deprecated public static final int focusedMonthDateColor = 16843587; // 0x1010343
-    field public static final int focusedSearchResultHighlightColor;
+    field public static final int focusedSearchResultHighlightColor = 16844419; // 0x1010683
     field public static final int font = 16844082; // 0x1010532
     field public static final int fontFamily = 16843692; // 0x10103ac
     field public static final int fontFeatureSettings = 16843959; // 0x10104b7
@@ -896,10 +896,10 @@
     field public static final int hand_secondTintMode = 16844349; // 0x101063d
     field public static final int handle = 16843354; // 0x101025a
     field public static final int handleProfiling = 16842786; // 0x1010022
-    field public static final int handwritingBoundsOffsetBottom;
-    field public static final int handwritingBoundsOffsetLeft;
-    field public static final int handwritingBoundsOffsetRight;
-    field public static final int handwritingBoundsOffsetTop;
+    field public static final int handwritingBoundsOffsetBottom = 16844406; // 0x1010676
+    field public static final int handwritingBoundsOffsetLeft = 16844403; // 0x1010673
+    field public static final int handwritingBoundsOffsetRight = 16844405; // 0x1010675
+    field public static final int handwritingBoundsOffsetTop = 16844404; // 0x1010674
     field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e
     field public static final int hardwareAccelerated = 16843475; // 0x10102d3
     field public static final int hasCode = 16842764; // 0x101000c
@@ -986,7 +986,7 @@
     field public static final int isAlwaysSyncable = 16843571; // 0x1010333
     field public static final int isAsciiCapable = 16843753; // 0x10103e9
     field public static final int isAuxiliary = 16843647; // 0x101037f
-    field public static final int isCredential;
+    field public static final int isCredential = 16844417; // 0x1010681
     field public static final int isDefault = 16843297; // 0x1010221
     field public static final int isFeatureSplit = 16844123; // 0x101055b
     field public static final int isGame = 16843764; // 0x10103f4
@@ -1021,8 +1021,8 @@
     field @Deprecated public static final int keyTextSize = 16843316; // 0x1010234
     field @Deprecated public static final int keyWidth = 16843325; // 0x101023d
     field public static final int keyboardLayout = 16843691; // 0x10103ab
-    field public static final int keyboardLayoutType;
-    field public static final int keyboardLocale;
+    field public static final int keyboardLayoutType = 16844415; // 0x101067f
+    field public static final int keyboardLocale = 16844414; // 0x101067e
     field @Deprecated public static final int keyboardMode = 16843341; // 0x101024d
     field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
     field public static final int keycode = 16842949; // 0x10100c5
@@ -1263,8 +1263,8 @@
     field public static final int persistentDrawingCache = 16842990; // 0x10100ee
     field public static final int persistentWhenFeatureAvailable = 16844131; // 0x1010563
     field @Deprecated public static final int phoneNumber = 16843111; // 0x1010167
-    field public static final int physicalKeyboardHintLanguageTag;
-    field public static final int physicalKeyboardHintLayoutType;
+    field public static final int physicalKeyboardHintLanguageTag = 16844411; // 0x101067b
+    field public static final int physicalKeyboardHintLayoutType = 16844412; // 0x101067c
     field public static final int pivotX = 16843189; // 0x10101b5
     field public static final int pivotY = 16843190; // 0x10101b6
     field public static final int pointerIcon = 16844041; // 0x1010509
@@ -1354,7 +1354,7 @@
     field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
     field public static final int required = 16843406; // 0x101028e
     field public static final int requiredAccountType = 16843734; // 0x10103d6
-    field public static final int requiredDisplayCategory;
+    field public static final int requiredDisplayCategory = 16844409; // 0x1010679
     field public static final int requiredFeature = 16844116; // 0x1010554
     field public static final int requiredForAllUsers = 16843728; // 0x10103d0
     field public static final int requiredNotFeature = 16844117; // 0x1010555
@@ -1422,7 +1422,7 @@
     field public static final int searchHintIcon = 16843988; // 0x10104d4
     field public static final int searchIcon = 16843907; // 0x1010483
     field public static final int searchMode = 16843221; // 0x10101d5
-    field public static final int searchResultHighlightColor;
+    field public static final int searchResultHighlightColor = 16844418; // 0x1010682
     field public static final int searchSettingsDescription = 16843402; // 0x101028a
     field public static final int searchSuggestAuthority = 16843222; // 0x10101d6
     field public static final int searchSuggestIntentAction = 16843225; // 0x10101d9
@@ -1449,7 +1449,7 @@
     field public static final int sessionService = 16843837; // 0x101043d
     field public static final int settingsActivity = 16843301; // 0x1010225
     field public static final int settingsSliceUri = 16844179; // 0x1010593
-    field public static final int settingsSubtitle;
+    field public static final int settingsSubtitle = 16844422; // 0x1010686
     field public static final int setupActivity = 16843766; // 0x10103f6
     field public static final int shadowColor = 16843105; // 0x1010161
     field public static final int shadowDx = 16843106; // 0x1010162
@@ -1555,7 +1555,7 @@
     field public static final int strokeLineJoin = 16843788; // 0x101040c
     field public static final int strokeMiterLimit = 16843789; // 0x101040d
     field public static final int strokeWidth = 16843783; // 0x1010407
-    field public static final int stylusHandwritingSettingsActivity;
+    field public static final int stylusHandwritingSettingsActivity = 16844420; // 0x1010684
     field public static final int subMenuArrow = 16844019; // 0x10104f3
     field public static final int submitBackground = 16843912; // 0x1010488
     field public static final int subtitle = 16843473; // 0x10102d1
@@ -1861,7 +1861,7 @@
     field public static final int windowMinWidthMajor = 16843606; // 0x1010356
     field public static final int windowMinWidthMinor = 16843607; // 0x1010357
     field public static final int windowNoDisplay = 16843294; // 0x101021e
-    field public static final int windowNoMoveAnimation;
+    field public static final int windowNoMoveAnimation = 16844421; // 0x1010685
     field public static final int windowNoTitle = 16842838; // 0x1010056
     field @Deprecated public static final int windowOverscan = 16843727; // 0x10103cf
     field public static final int windowReenterTransition = 16843951; // 0x10104af
@@ -1966,18 +1966,18 @@
     field public static final int system_accent3_700 = 17170522; // 0x106005a
     field public static final int system_accent3_800 = 17170523; // 0x106005b
     field public static final int system_accent3_900 = 17170524; // 0x106005c
-    field public static final int system_background_dark;
-    field public static final int system_background_light;
-    field public static final int system_control_activated_dark;
-    field public static final int system_control_activated_light;
-    field public static final int system_control_highlight_dark;
-    field public static final int system_control_highlight_light;
-    field public static final int system_control_normal_dark;
-    field public static final int system_control_normal_light;
-    field public static final int system_error_container_dark;
-    field public static final int system_error_container_light;
-    field public static final int system_error_dark;
-    field public static final int system_error_light;
+    field public static final int system_background_dark = 17170581; // 0x1060095
+    field public static final int system_background_light = 17170538; // 0x106006a
+    field public static final int system_control_activated_dark = 17170599; // 0x10600a7
+    field public static final int system_control_activated_light = 17170556; // 0x106007c
+    field public static final int system_control_highlight_dark = 17170601; // 0x10600a9
+    field public static final int system_control_highlight_light = 17170558; // 0x106007e
+    field public static final int system_control_normal_dark = 17170600; // 0x10600a8
+    field public static final int system_control_normal_light = 17170557; // 0x106007d
+    field public static final int system_error_container_dark = 17170597; // 0x10600a5
+    field public static final int system_error_container_light = 17170554; // 0x106007a
+    field public static final int system_error_dark = 17170595; // 0x10600a3
+    field public static final int system_error_light = 17170552; // 0x1060078
     field public static final int system_neutral1_0 = 17170461; // 0x106001d
     field public static final int system_neutral1_10 = 17170462; // 0x106001e
     field public static final int system_neutral1_100 = 17170464; // 0x1060020
@@ -2004,94 +2004,94 @@
     field public static final int system_neutral2_700 = 17170483; // 0x1060033
     field public static final int system_neutral2_800 = 17170484; // 0x1060034
     field public static final int system_neutral2_900 = 17170485; // 0x1060035
-    field public static final int system_on_background_dark;
-    field public static final int system_on_background_light;
-    field public static final int system_on_error_container_dark;
-    field public static final int system_on_error_container_light;
-    field public static final int system_on_error_dark;
-    field public static final int system_on_error_light;
-    field public static final int system_on_primary_container_dark;
-    field public static final int system_on_primary_container_light;
-    field public static final int system_on_primary_dark;
-    field public static final int system_on_primary_fixed;
-    field public static final int system_on_primary_fixed_variant;
-    field public static final int system_on_primary_light;
-    field public static final int system_on_secondary_container_dark;
-    field public static final int system_on_secondary_container_light;
-    field public static final int system_on_secondary_dark;
-    field public static final int system_on_secondary_fixed;
-    field public static final int system_on_secondary_fixed_variant;
-    field public static final int system_on_secondary_light;
-    field public static final int system_on_surface_dark;
-    field public static final int system_on_surface_light;
-    field public static final int system_on_surface_variant_dark;
-    field public static final int system_on_surface_variant_light;
-    field public static final int system_on_tertiary_container_dark;
-    field public static final int system_on_tertiary_container_light;
-    field public static final int system_on_tertiary_dark;
-    field public static final int system_on_tertiary_fixed;
-    field public static final int system_on_tertiary_fixed_variant;
-    field public static final int system_on_tertiary_light;
-    field public static final int system_outline_dark;
-    field public static final int system_outline_light;
-    field public static final int system_outline_variant_dark;
-    field public static final int system_outline_variant_light;
-    field public static final int system_palette_key_color_neutral_dark;
-    field public static final int system_palette_key_color_neutral_light;
-    field public static final int system_palette_key_color_neutral_variant_dark;
-    field public static final int system_palette_key_color_neutral_variant_light;
-    field public static final int system_palette_key_color_primary_dark;
-    field public static final int system_palette_key_color_primary_light;
-    field public static final int system_palette_key_color_secondary_dark;
-    field public static final int system_palette_key_color_secondary_light;
-    field public static final int system_palette_key_color_tertiary_dark;
-    field public static final int system_palette_key_color_tertiary_light;
-    field public static final int system_primary_container_dark;
-    field public static final int system_primary_container_light;
-    field public static final int system_primary_dark;
-    field public static final int system_primary_fixed;
-    field public static final int system_primary_fixed_dim;
-    field public static final int system_primary_light;
-    field public static final int system_secondary_container_dark;
-    field public static final int system_secondary_container_light;
-    field public static final int system_secondary_dark;
-    field public static final int system_secondary_fixed;
-    field public static final int system_secondary_fixed_dim;
-    field public static final int system_secondary_light;
-    field public static final int system_surface_bright_dark;
-    field public static final int system_surface_bright_light;
-    field public static final int system_surface_container_dark;
-    field public static final int system_surface_container_high_dark;
-    field public static final int system_surface_container_high_light;
-    field public static final int system_surface_container_highest_dark;
-    field public static final int system_surface_container_highest_light;
-    field public static final int system_surface_container_light;
-    field public static final int system_surface_container_low_dark;
-    field public static final int system_surface_container_low_light;
-    field public static final int system_surface_container_lowest_dark;
-    field public static final int system_surface_container_lowest_light;
-    field public static final int system_surface_dark;
-    field public static final int system_surface_dim_dark;
-    field public static final int system_surface_dim_light;
-    field public static final int system_surface_light;
-    field public static final int system_surface_variant_dark;
-    field public static final int system_surface_variant_light;
-    field public static final int system_tertiary_container_dark;
-    field public static final int system_tertiary_container_light;
-    field public static final int system_tertiary_dark;
-    field public static final int system_tertiary_fixed;
-    field public static final int system_tertiary_fixed_dim;
-    field public static final int system_tertiary_light;
-    field public static final int system_text_hint_inverse_dark;
-    field public static final int system_text_hint_inverse_light;
-    field public static final int system_text_primary_inverse_dark;
-    field public static final int system_text_primary_inverse_disable_only_dark;
-    field public static final int system_text_primary_inverse_disable_only_light;
-    field public static final int system_text_primary_inverse_light;
-    field public static final int system_text_secondary_and_tertiary_inverse_dark;
-    field public static final int system_text_secondary_and_tertiary_inverse_disabled_dark;
-    field public static final int system_text_secondary_and_tertiary_inverse_disabled_light;
-    field public static final int system_text_secondary_and_tertiary_inverse_light;
+    field public static final int system_on_background_dark = 17170582; // 0x1060096
+    field public static final int system_on_background_light = 17170539; // 0x106006b
+    field public static final int system_on_error_container_dark = 17170598; // 0x10600a6
+    field public static final int system_on_error_container_light = 17170555; // 0x106007b
+    field public static final int system_on_error_dark = 17170596; // 0x10600a4
+    field public static final int system_on_error_light = 17170553; // 0x1060079
+    field public static final int system_on_primary_container_dark = 17170570; // 0x106008a
+    field public static final int system_on_primary_container_light = 17170527; // 0x106005f
+    field public static final int system_on_primary_dark = 17170572; // 0x106008c
+    field public static final int system_on_primary_fixed = 17170614; // 0x10600b6
+    field public static final int system_on_primary_fixed_variant = 17170615; // 0x10600b7
+    field public static final int system_on_primary_light = 17170529; // 0x1060061
+    field public static final int system_on_secondary_container_dark = 17170574; // 0x106008e
+    field public static final int system_on_secondary_container_light = 17170531; // 0x1060063
+    field public static final int system_on_secondary_dark = 17170576; // 0x1060090
+    field public static final int system_on_secondary_fixed = 17170618; // 0x10600ba
+    field public static final int system_on_secondary_fixed_variant = 17170619; // 0x10600bb
+    field public static final int system_on_secondary_light = 17170533; // 0x1060065
+    field public static final int system_on_surface_dark = 17170584; // 0x1060098
+    field public static final int system_on_surface_light = 17170541; // 0x106006d
+    field public static final int system_on_surface_variant_dark = 17170593; // 0x10600a1
+    field public static final int system_on_surface_variant_light = 17170550; // 0x1060076
+    field public static final int system_on_tertiary_container_dark = 17170578; // 0x1060092
+    field public static final int system_on_tertiary_container_light = 17170535; // 0x1060067
+    field public static final int system_on_tertiary_dark = 17170580; // 0x1060094
+    field public static final int system_on_tertiary_fixed = 17170622; // 0x10600be
+    field public static final int system_on_tertiary_fixed_variant = 17170623; // 0x10600bf
+    field public static final int system_on_tertiary_light = 17170537; // 0x1060069
+    field public static final int system_outline_dark = 17170594; // 0x10600a2
+    field public static final int system_outline_light = 17170551; // 0x1060077
+    field public static final int system_outline_variant_dark = 17170625; // 0x10600c1
+    field public static final int system_outline_variant_light = 17170624; // 0x10600c0
+    field public static final int system_palette_key_color_neutral_dark = 17170610; // 0x10600b2
+    field public static final int system_palette_key_color_neutral_light = 17170567; // 0x1060087
+    field public static final int system_palette_key_color_neutral_variant_dark = 17170611; // 0x10600b3
+    field public static final int system_palette_key_color_neutral_variant_light = 17170568; // 0x1060088
+    field public static final int system_palette_key_color_primary_dark = 17170607; // 0x10600af
+    field public static final int system_palette_key_color_primary_light = 17170564; // 0x1060084
+    field public static final int system_palette_key_color_secondary_dark = 17170608; // 0x10600b0
+    field public static final int system_palette_key_color_secondary_light = 17170565; // 0x1060085
+    field public static final int system_palette_key_color_tertiary_dark = 17170609; // 0x10600b1
+    field public static final int system_palette_key_color_tertiary_light = 17170566; // 0x1060086
+    field public static final int system_primary_container_dark = 17170569; // 0x1060089
+    field public static final int system_primary_container_light = 17170526; // 0x106005e
+    field public static final int system_primary_dark = 17170571; // 0x106008b
+    field public static final int system_primary_fixed = 17170612; // 0x10600b4
+    field public static final int system_primary_fixed_dim = 17170613; // 0x10600b5
+    field public static final int system_primary_light = 17170528; // 0x1060060
+    field public static final int system_secondary_container_dark = 17170573; // 0x106008d
+    field public static final int system_secondary_container_light = 17170530; // 0x1060062
+    field public static final int system_secondary_dark = 17170575; // 0x106008f
+    field public static final int system_secondary_fixed = 17170616; // 0x10600b8
+    field public static final int system_secondary_fixed_dim = 17170617; // 0x10600b9
+    field public static final int system_secondary_light = 17170532; // 0x1060064
+    field public static final int system_surface_bright_dark = 17170590; // 0x106009e
+    field public static final int system_surface_bright_light = 17170547; // 0x1060073
+    field public static final int system_surface_container_dark = 17170587; // 0x106009b
+    field public static final int system_surface_container_high_dark = 17170588; // 0x106009c
+    field public static final int system_surface_container_high_light = 17170545; // 0x1060071
+    field public static final int system_surface_container_highest_dark = 17170589; // 0x106009d
+    field public static final int system_surface_container_highest_light = 17170546; // 0x1060072
+    field public static final int system_surface_container_light = 17170544; // 0x1060070
+    field public static final int system_surface_container_low_dark = 17170585; // 0x1060099
+    field public static final int system_surface_container_low_light = 17170542; // 0x106006e
+    field public static final int system_surface_container_lowest_dark = 17170586; // 0x106009a
+    field public static final int system_surface_container_lowest_light = 17170543; // 0x106006f
+    field public static final int system_surface_dark = 17170583; // 0x1060097
+    field public static final int system_surface_dim_dark = 17170591; // 0x106009f
+    field public static final int system_surface_dim_light = 17170548; // 0x1060074
+    field public static final int system_surface_light = 17170540; // 0x106006c
+    field public static final int system_surface_variant_dark = 17170592; // 0x10600a0
+    field public static final int system_surface_variant_light = 17170549; // 0x1060075
+    field public static final int system_tertiary_container_dark = 17170577; // 0x1060091
+    field public static final int system_tertiary_container_light = 17170534; // 0x1060066
+    field public static final int system_tertiary_dark = 17170579; // 0x1060093
+    field public static final int system_tertiary_fixed = 17170620; // 0x10600bc
+    field public static final int system_tertiary_fixed_dim = 17170621; // 0x10600bd
+    field public static final int system_tertiary_light = 17170536; // 0x1060068
+    field public static final int system_text_hint_inverse_dark = 17170606; // 0x10600ae
+    field public static final int system_text_hint_inverse_light = 17170563; // 0x1060083
+    field public static final int system_text_primary_inverse_dark = 17170602; // 0x10600aa
+    field public static final int system_text_primary_inverse_disable_only_dark = 17170604; // 0x10600ac
+    field public static final int system_text_primary_inverse_disable_only_light = 17170561; // 0x1060081
+    field public static final int system_text_primary_inverse_light = 17170559; // 0x106007f
+    field public static final int system_text_secondary_and_tertiary_inverse_dark = 17170603; // 0x10600ab
+    field public static final int system_text_secondary_and_tertiary_inverse_disabled_dark = 17170605; // 0x10600ad
+    field public static final int system_text_secondary_and_tertiary_inverse_disabled_light = 17170562; // 0x1060082
+    field public static final int system_text_secondary_and_tertiary_inverse_light = 17170560; // 0x1060080
     field public static final int tab_indicator_text = 17170441; // 0x1060009
     field @Deprecated public static final int tertiary_text_dark = 17170448; // 0x1060010
     field @Deprecated public static final int tertiary_text_light = 17170449; // 0x1060011
@@ -2310,7 +2310,7 @@
     field public static final int accessibilityActionPageUp = 16908358; // 0x1020046
     field public static final int accessibilityActionPressAndHold = 16908362; // 0x102004a
     field public static final int accessibilityActionScrollDown = 16908346; // 0x102003a
-    field public static final int accessibilityActionScrollInDirection;
+    field public static final int accessibilityActionScrollInDirection = 16908382; // 0x102005e
     field public static final int accessibilityActionScrollLeft = 16908345; // 0x1020039
     field public static final int accessibilityActionScrollRight = 16908347; // 0x102003b
     field public static final int accessibilityActionScrollToPosition = 16908343; // 0x1020037
@@ -2331,7 +2331,7 @@
     field public static final int addToDictionary = 16908330; // 0x102002a
     field public static final int autofill = 16908355; // 0x1020043
     field public static final int background = 16908288; // 0x1020000
-    field public static final int bold;
+    field public static final int bold = 16908379; // 0x102005b
     field public static final int button1 = 16908313; // 0x1020019
     field public static final int button2 = 16908314; // 0x102001a
     field public static final int button3 = 16908315; // 0x102001b
@@ -2357,7 +2357,7 @@
     field public static final int inputExtractAccessories = 16908378; // 0x102005a
     field public static final int inputExtractAction = 16908377; // 0x1020059
     field public static final int inputExtractEditText = 16908325; // 0x1020025
-    field public static final int italic;
+    field public static final int italic = 16908380; // 0x102005c
     field @Deprecated public static final int keyboardView = 16908326; // 0x1020026
     field public static final int list = 16908298; // 0x102000a
     field public static final int list_container = 16908351; // 0x102003f
@@ -2389,7 +2389,7 @@
     field public static final int textAssist = 16908353; // 0x1020041
     field public static final int title = 16908310; // 0x1020016
     field public static final int toggle = 16908311; // 0x1020017
-    field public static final int underline;
+    field public static final int underline = 16908381; // 0x102005d
     field public static final int undo = 16908338; // 0x1020032
     field public static final int widget_frame = 16908312; // 0x1020018
   }
@@ -13679,7 +13679,6 @@
   }
 
   public final class CredentialOption implements android.os.Parcelable {
-    ctor @Deprecated public CredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
     method public int describeContents();
     method @NonNull public java.util.Set<android.content.ComponentName> getAllowedProviders();
     method @NonNull public android.os.Bundle getCandidateQueryData();
@@ -32671,7 +32670,7 @@
     field public static final int S = 31; // 0x1f
     field public static final int S_V2 = 32; // 0x20
     field public static final int TIRAMISU = 33; // 0x21
-    field public static final int UPSIDE_DOWN_CAKE = 10000; // 0x2710
+    field public static final int UPSIDE_DOWN_CAKE = 34; // 0x22
     field public static final int VANILLA_ICE_CREAM = 10000; // 0x2710
   }
 
@@ -51615,7 +51614,7 @@
     field public static final int TYPE_CONTEXT_MENU = 1001; // 0x3e9
     field public static final int TYPE_COPY = 1011; // 0x3f3
     field public static final int TYPE_CROSSHAIR = 1007; // 0x3ef
-    field public static final int TYPE_DEFAULT = 1000; // 0x3e8
+    field @Deprecated public static final int TYPE_DEFAULT = 1000; // 0x3e8
     field public static final int TYPE_GRAB = 1020; // 0x3fc
     field public static final int TYPE_GRABBING = 1021; // 0x3fd
     field public static final int TYPE_HAND = 1002; // 0x3ea
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0f51249..dab8a10 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -411,14 +411,14 @@
     field public static final int sdkVersion = 16844304; // 0x1010610
     field public static final int supportsAmbientMode = 16844173; // 0x101058d
     field public static final int userRestriction = 16844164; // 0x1010584
-    field public static final int visualQueryDetectionService;
+    field public static final int visualQueryDetectionService = 16844410; // 0x101067a
   }
 
   public static final class R.bool {
-    field public static final int config_enableDefaultNotes;
-    field public static final int config_enableDefaultNotesForWorkProfile;
+    field public static final int config_enableDefaultNotes = 17891338; // 0x111000a
+    field public static final int config_enableDefaultNotesForWorkProfile = 17891339; // 0x111000b
     field public static final int config_enableQrCodeScannerOnLockScreen = 17891336; // 0x1110008
-    field public static final int config_safetyProtectionEnabled;
+    field public static final int config_safetyProtectionEnabled = 17891337; // 0x1110009
     field public static final int config_sendPackageName = 17891328; // 0x1110000
     field public static final int config_showDefaultAssistant = 17891329; // 0x1110001
     field public static final int config_showDefaultEmergency = 17891330; // 0x1110002
@@ -431,7 +431,7 @@
 
   public static final class R.dimen {
     field public static final int config_restrictedIconSize = 17104903; // 0x1050007
-    field public static final int config_viewConfigurationHandwritingGestureLineMargin;
+    field public static final int config_viewConfigurationHandwritingGestureLineMargin = 17104906; // 0x105000a
   }
 
   public static final class R.drawable {
@@ -453,7 +453,7 @@
     field public static final int config_defaultCallRedirection = 17039397; // 0x1040025
     field public static final int config_defaultCallScreening = 17039398; // 0x1040026
     field public static final int config_defaultDialer = 17039395; // 0x1040023
-    field public static final int config_defaultNotes;
+    field public static final int config_defaultNotes = 17039429; // 0x1040045
     field public static final int config_defaultSms = 17039396; // 0x1040024
     field public static final int config_devicePolicyManagement = 17039421; // 0x104003d
     field public static final int config_feedbackIntentExtraKey = 17039391; // 0x104001f
@@ -469,10 +469,10 @@
     field public static final int config_systemAutomotiveCalendarSyncManager = 17039423; // 0x104003f
     field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
     field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
-    field public static final int config_systemCallStreaming;
+    field public static final int config_systemCallStreaming = 17039431; // 0x1040047
     field public static final int config_systemCompanionDeviceProvider = 17039417; // 0x1040039
     field public static final int config_systemContacts = 17039403; // 0x104002b
-    field public static final int config_systemFinancedDeviceController;
+    field public static final int config_systemFinancedDeviceController = 17039430; // 0x1040046
     field public static final int config_systemGallery = 17039399; // 0x1040027
     field public static final int config_systemNotificationIntelligence = 17039413; // 0x1040035
     field public static final int config_systemSettingsIntelligence = 17039426; // 0x1040042
@@ -484,7 +484,7 @@
     field public static final int config_systemUi = 17039418; // 0x104003a
     field public static final int config_systemUiIntelligence = 17039410; // 0x1040032
     field public static final int config_systemVisualIntelligence = 17039415; // 0x1040037
-    field public static final int config_systemWearHealthService;
+    field public static final int config_systemWearHealthService = 17039428; // 0x1040044
     field public static final int config_systemWellbeing = 17039408; // 0x1040030
     field public static final int config_systemWifiCoexManager = 17039407; // 0x104002f
     field public static final int safety_protection_display_text = 17039425; // 0x1040041
@@ -10107,7 +10107,7 @@
   public final class NetworkProviderInfo implements android.os.Parcelable {
     method public int describeContents();
     method @IntRange(from=0, to=100) public int getBatteryPercentage();
-    method @IntRange(from=0, to=3) public int getConnectionStrength();
+    method @IntRange(from=0, to=4) public int getConnectionStrength();
     method @NonNull public String getDeviceName();
     method public int getDeviceType();
     method @NonNull public android.os.Bundle getExtras();
@@ -10126,7 +10126,7 @@
     ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build();
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int);
-    method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=3) int);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceType(int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setExtras(@NonNull android.os.Bundle);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index b1f779f..0420e69 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -8,8 +8,6 @@
     field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
     field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
     field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE";
-    field public static final String BODY_SENSORS_WRIST_TEMPERATURE = "android.permission.BODY_SENSORS_WRIST_TEMPERATURE";
-    field public static final String BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND = "android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND";
     field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
     field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
     field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
@@ -338,10 +336,12 @@
   }
 
   public class Notification implements android.os.Parcelable {
+    method public boolean isUserInitiatedJob();
     method public boolean shouldShowForegroundImmediately();
     field public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice";
     field public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon";
     field public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent";
+    field public static final int FLAG_USER_INITIATED_JOB = 32768; // 0x8000
   }
 
   public final class NotificationChannel implements android.os.Parcelable {
@@ -351,10 +351,10 @@
     method public void setDeleted(boolean);
     method public void setDeletedTimeMs(long);
     method public void setDemoted(boolean);
-    method public void setFgServiceShown(boolean);
     method public void setImportanceLockedByCriticalDeviceFunction(boolean);
     method public void setImportantConversation(boolean);
     method public void setOriginalImportance(int);
+    method public void setUserVisibleTaskShown(boolean);
   }
 
   public final class NotificationChannelGroup implements android.os.Parcelable {
@@ -1649,6 +1649,11 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.KeyphraseMetadata> CREATOR;
   }
 
+  public class SoundTrigger {
+    field public static final int MODEL_PARAM_INVALID = -1; // 0xffffffff
+    field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
+  }
+
   public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable {
     ctor public SoundTrigger.KeyphraseRecognitionExtra(int, int, int);
   }
@@ -1661,6 +1666,19 @@
     ctor public SoundTrigger.ModuleProperties(int, @NonNull String, @NonNull String, @NonNull String, int, @NonNull String, int, int, int, int, boolean, int, boolean, int, boolean, int);
   }
 
+  public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable {
+    ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int);
+    ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[]);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.RecognitionConfig> CREATOR;
+    field public final boolean allowMultipleTriggers;
+    field public final int audioCapabilities;
+    field public final boolean captureRequested;
+    field @NonNull public final byte[] data;
+    field @NonNull public final android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[] keyphrases;
+  }
+
   public static class SoundTrigger.RecognitionEvent {
     ctor public SoundTrigger.RecognitionEvent(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[], long);
   }
@@ -1998,6 +2016,57 @@
 
 }
 
+package android.media.soundtrigger {
+
+  public final class SoundTriggerInstrumentation {
+    method public void setResourceContention(boolean);
+    method public void triggerOnResourcesAvailable();
+    method public void triggerRestart();
+  }
+
+  public static interface SoundTriggerInstrumentation.GlobalCallback {
+    method public default void onClientAttached();
+    method public default void onClientDetached();
+    method public default void onFrameworkDetached();
+    method public void onModelLoaded(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelSession);
+    method public default void onPreempted();
+    method public default void onRestarted();
+  }
+
+  public static interface SoundTriggerInstrumentation.ModelCallback {
+    method public default void onModelUnloaded();
+    method public default void onParamSet(int, int);
+    method public void onRecognitionStarted(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionSession);
+  }
+
+  public class SoundTriggerInstrumentation.ModelSession {
+    method public void clearModelCallback();
+    method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.Keyphrase> getPhrases();
+    method @NonNull public android.media.soundtrigger.SoundTriggerManager.Model getSoundModel();
+    method public boolean isKeyphrase();
+    method public void setModelCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelCallback);
+    method public void triggerUnloadModel();
+  }
+
+  public static interface SoundTriggerInstrumentation.RecognitionCallback {
+    method public void onRecognitionStopped();
+  }
+
+  public class SoundTriggerInstrumentation.RecognitionSession {
+    method public void clearRecognitionCallback();
+    method public int getAudioSession();
+    method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig getRecognitionConfig();
+    method public void setRecognitionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionCallback);
+    method public void triggerAbortRecognition();
+    method public void triggerRecognitionEvent(@NonNull byte[], @Nullable java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
+  }
+
+  public final class SoundTriggerManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public static android.media.soundtrigger.SoundTriggerInstrumentation attachInstrumentation(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.GlobalCallback);
+  }
+
+}
+
 package android.media.tv {
 
   public final class TvInputManager {
@@ -2022,6 +2091,14 @@
 
 }
 
+package android.media.voice {
+
+  public final class KeyphraseModelManager {
+    method @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public void setModelDatabaseForTestEnabled(boolean);
+  }
+
+}
+
 package android.net {
 
   public class NetworkPolicyManager {
@@ -2275,12 +2352,12 @@
     method public int getMainDisplayIdAssignedToUser();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.Set<java.lang.String> getPreInstallableSystemPackages(@NonNull String);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType();
-    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
+    method @Deprecated @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isUserTypeEnabled(@NonNull String);
     method public boolean isVisibleBackgroundUsersOnDefaultDisplaySupported();
     method public boolean isVisibleBackgroundUsersSupported();
-    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
+    method @Deprecated @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
   }
 
   public final class VibrationAttributes implements android.os.Parcelable {
@@ -2942,6 +3019,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
     method @NonNull public final java.util.List<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> listModuleProperties();
+    method public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean);
   }
 
   public static class VoiceInteractionSession.ActivityId {
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 3615435..08a1af4 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -2878,9 +2878,7 @@
 
         public IAccessibilityServiceClientWrapper(Context context, Looper looper,
                 Callbacks callback) {
-            mCallback = callback;
-            mContext = context;
-            mExecutor = new HandlerExecutor(new Handler(looper));
+            this(context, new HandlerExecutor(new Handler(looper)), callback);
         }
 
         public void init(IAccessibilityServiceConnection connection, int connectionId,
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 0293bb5..95e446d 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -343,7 +343,170 @@
      */
     public abstract boolean hasRunningActivity(int uid, @Nullable String packageName);
 
-    public abstract void updateOomAdj();
+    /**
+     * Oom Adj Reason: none - internal use only, do not use it.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_NONE = 0;
+
+    /**
+     * Oom Adj Reason: activity changes.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_ACTIVITY = 1;
+
+    /**
+     * Oom Adj Reason: finishing a broadcast receiver.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2;
+
+    /**
+     * Oom Adj Reason: starting a broadcast receiver.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_START_RECEIVER = 3;
+
+    /**
+     * Oom Adj Reason: binding to a service.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_BIND_SERVICE = 4;
+
+    /**
+     * Oom Adj Reason: unbinding from a service.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5;
+
+    /**
+     * Oom Adj Reason: starting a service.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_START_SERVICE = 6;
+
+    /**
+     * Oom Adj Reason: connecting to a content provider.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_GET_PROVIDER = 7;
+
+    /**
+     * Oom Adj Reason: disconnecting from a content provider.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8;
+
+    /**
+     * Oom Adj Reason: UI visibility changes.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_UI_VISIBILITY = 9;
+
+    /**
+     * Oom Adj Reason: device power allowlist changes.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_ALLOWLIST = 10;
+
+    /**
+     * Oom Adj Reason: starting a process.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11;
+
+    /**
+     * Oom Adj Reason: ending a process.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_PROCESS_END = 12;
+
+    /**
+     * Oom Adj Reason: short FGS timeout.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13;
+
+    /**
+     * Oom Adj Reason: system initialization.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_SYSTEM_INIT = 14;
+
+    /**
+     * Oom Adj Reason: backup/restore.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_BACKUP = 15;
+
+    /**
+     * Oom Adj Reason: instrumented by the SHELL.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_SHELL = 16;
+
+    /**
+     * Oom Adj Reason: task stack is being removed.
+     */
+    public static final int OOM_ADJ_REASON_REMOVE_TASK = 17;
+
+    /**
+     * Oom Adj Reason: uid idle.
+     */
+    public static final int OOM_ADJ_REASON_UID_IDLE = 18;
+
+    /**
+     * Oom Adj Reason: stop service.
+     */
+    public static final int OOM_ADJ_REASON_STOP_SERVICE = 19;
+
+    /**
+     * Oom Adj Reason: executing service.
+     */
+    public static final int OOM_ADJ_REASON_EXECUTING_SERVICE = 20;
+
+    /**
+     * Oom Adj Reason: background restriction changes.
+     */
+    public static final int OOM_ADJ_REASON_RESTRICTION_CHANGE = 21;
+
+    /**
+     * Oom Adj Reason: A package or its component is disabled.
+     */
+    public static final int OOM_ADJ_REASON_COMPONENT_DISABLED = 22;
+
+    @IntDef(prefix = {"OOM_ADJ_REASON_"}, value = {
+        OOM_ADJ_REASON_NONE,
+        OOM_ADJ_REASON_ACTIVITY,
+        OOM_ADJ_REASON_FINISH_RECEIVER,
+        OOM_ADJ_REASON_START_RECEIVER,
+        OOM_ADJ_REASON_BIND_SERVICE,
+        OOM_ADJ_REASON_UNBIND_SERVICE,
+        OOM_ADJ_REASON_START_SERVICE,
+        OOM_ADJ_REASON_GET_PROVIDER,
+        OOM_ADJ_REASON_REMOVE_PROVIDER,
+        OOM_ADJ_REASON_UI_VISIBILITY,
+        OOM_ADJ_REASON_ALLOWLIST,
+        OOM_ADJ_REASON_PROCESS_BEGIN,
+        OOM_ADJ_REASON_PROCESS_END,
+        OOM_ADJ_REASON_SHORT_FGS_TIMEOUT,
+        OOM_ADJ_REASON_SYSTEM_INIT,
+        OOM_ADJ_REASON_BACKUP,
+        OOM_ADJ_REASON_SHELL,
+        OOM_ADJ_REASON_REMOVE_TASK,
+        OOM_ADJ_REASON_UID_IDLE,
+        OOM_ADJ_REASON_STOP_SERVICE,
+        OOM_ADJ_REASON_EXECUTING_SERVICE,
+        OOM_ADJ_REASON_RESTRICTION_CHANGE,
+        OOM_ADJ_REASON_COMPONENT_DISABLED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface OomAdjReason {}
+
+    /**
+     * Request to update oom adj.
+     */
+    public abstract void updateOomAdj(@OomAdjReason int oomAdjReason);
     public abstract void updateCpuStats();
 
     /**
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ba61fad..a50a776 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -41,6 +41,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.RemoteServiceException.BadForegroundServiceNotificationException;
+import android.app.RemoteServiceException.BadUserInitiatedJobNotificationException;
 import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
 import android.app.RemoteServiceException.CrashedByAdbException;
 import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException;
@@ -2100,6 +2101,9 @@
             case BadForegroundServiceNotificationException.TYPE_ID:
                 throw new BadForegroundServiceNotificationException(message);
 
+            case BadUserInitiatedJobNotificationException.TYPE_ID:
+                throw new BadUserInitiatedJobNotificationException(message);
+
             case MissingRequestPasswordComplexityPermissionException.TYPE_ID:
                 throw new MissingRequestPasswordComplexityPermissionException(message);
 
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index b48a8fb..3312294 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1450,9 +1450,8 @@
     public static final int OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD =
             AppProtoEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD;
 
-    /** @hide Access to wrist temperature sensors. */
-    public static final int OP_BODY_SENSORS_WRIST_TEMPERATURE =
-            AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE;
+    // App op deprecated/removed.
+    private static final int OP_DEPRECATED_2 = AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE;
 
     /**
      * Send an intent to launch instead of posting the notification to the status bar.
@@ -1461,9 +1460,25 @@
      */
     public static final int OP_USE_FULL_SCREEN_INTENT = AppProtoEnums.APP_OP_USE_FULL_SCREEN_INTENT;
 
+    /**
+     * Hides camera indicator for sandboxed detection apps that directly access the service.
+     *
+     * @hide
+     */
+    public static final int OP_CAMERA_SANDBOXED =
+            AppProtoEnums.APP_OP_CAMERA_SANDBOXED;
+
+    /**
+     * Hides microphone indicator for sandboxed detection apps that directly access the service.
+     *
+     * @hide
+     */
+    public static final int OP_RECORD_AUDIO_SANDBOXED =
+            AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 134;
+    public static final int _NUM_OP = 136;
 
     /**
      * All app ops represented as strings.
@@ -1603,8 +1618,9 @@
             OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION,
             OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
             OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
-            OPSTR_BODY_SENSORS_WRIST_TEMPERATURE,
             OPSTR_USE_FULL_SCREEN_INTENT,
+            OPSTR_CAMERA_SANDBOXED,
+            OPSTR_RECORD_AUDIO_SANDBOXED
     })
     public @interface AppOpString {}
 
@@ -2013,6 +2029,20 @@
     public static final String OPSTR_COARSE_LOCATION_SOURCE = "android:coarse_location_source";
 
     /**
+     * Camera is being recorded in sandboxed detection process.
+     *
+     * @hide
+     */
+    public static final String OPSTR_CAMERA_SANDBOXED = "android:camera_sandboxed";
+
+    /**
+     * Audio is being recorded in sandboxed detection process.
+     *
+     * @hide
+     */
+    public static final String OPSTR_RECORD_AUDIO_SANDBOXED = "android:record_audio_sandboxed";
+
+    /**
      * Allow apps to create the requests to manage the media files without user confirmation.
      *
      * @see android.Manifest.permission#MANAGE_MEDIA
@@ -2189,11 +2219,10 @@
             "android:capture_consentless_bugreport_on_userdebug_build";
 
     /**
-     * Access to wrist temperature body sensors.
+     * App op deprecated/removed.
      * @hide
      */
-    public static final String OPSTR_BODY_SENSORS_WRIST_TEMPERATURE =
-            "android:body_sensors_wrist_temperature";
+    public static final String OPSTR_DEPRECATED_2 = "android:deprecated_2";
 
     /**
      * Send an intent to launch instead of posting the notification to the status bar.
@@ -2311,7 +2340,6 @@
             OP_READ_MEDIA_VISUAL_USER_SELECTED,
             OP_FOREGROUND_SERVICE_SPECIAL_USE,
             OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
-            OP_BODY_SENSORS_WRIST_TEMPERATURE,
             OP_USE_FULL_SCREEN_INTENT
     };
 
@@ -2731,14 +2759,15 @@
                 "CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD")
                 .setPermission(Manifest.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD)
                 .build(),
-        new AppOpInfo.Builder(OP_BODY_SENSORS_WRIST_TEMPERATURE,
-                OPSTR_BODY_SENSORS_WRIST_TEMPERATURE,
-                "BODY_SENSORS_WRIST_TEMPERATURE")
-                .setPermission(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE)
-                .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_DEPRECATED_2, OPSTR_DEPRECATED_2, "DEPRECATED_2")
+                .setDefaultMode(AppOpsManager.MODE_IGNORED).build(),
         new AppOpInfo.Builder(OP_USE_FULL_SCREEN_INTENT, OPSTR_USE_FULL_SCREEN_INTENT,
                 "USE_FULL_SCREEN_INTENT").setPermission(Manifest.permission.USE_FULL_SCREEN_INTENT)
-                .build()
+                .build(),
+        new AppOpInfo.Builder(OP_CAMERA_SANDBOXED, OPSTR_CAMERA_SANDBOXED,
+            "CAMERA_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_RECORD_AUDIO_SANDBOXED, OPSTR_RECORD_AUDIO_SANDBOXED,
+                "RECORD_AUDIO_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build()
     };
 
     // The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index be012cf..c0c59a2 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -473,7 +473,6 @@
             new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
                 new RegularPermission(Manifest.permission.ACTIVITY_RECOGNITION),
                 new RegularPermission(Manifest.permission.BODY_SENSORS),
-                new RegularPermission(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE),
                 new RegularPermission(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS),
             }, false),
             FGS_TYPE_PERM_ENFORCEMENT_FLAG_HEALTH /* permissionEnforcementFlag */,
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index f31132a..7f38b27 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -24,6 +24,7 @@
 import android.app.NotificationChannelGroup;
 import android.app.NotificationHistory;
 import android.app.NotificationManager;
+import android.content.AttributionSource;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
@@ -228,6 +229,7 @@
     void setNotificationDelegate(String callingPkg, String delegate);
     String getNotificationDelegate(String callingPkg);
     boolean canNotifyAsPackage(String callingPkg, String targetPkg, int userId);
+    boolean canUseFullScreenIntent(in AttributionSource attributionSource);
 
     void setPrivateNotificationsAllowed(boolean allow);
     boolean getPrivateNotificationsAllowed();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 1cad30c..f15df5a 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -722,6 +722,16 @@
      */
     public static final int FLAG_FSI_REQUESTED_BUT_DENIED = 0x00004000;
 
+    /**
+     * Bit to be bitwise-ored into the {@link #flags} field that should be
+     * set if this notification represents a currently running user-initiated job.
+     *
+     * This flag is for internal use only; applications cannot set this flag directly.
+     * @hide
+     */
+    @TestApi
+    public static final int FLAG_USER_INITIATED_JOB = 0x00008000;
+
     private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
             BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
             DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
@@ -731,7 +741,8 @@
     @IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT,
             FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE,
             FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY,
-            FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE})
+            FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE,
+            FLAG_USER_INITIATED_JOB})
     @Retention(RetentionPolicy.SOURCE)
     public @interface NotificationFlags{};
 
@@ -5727,7 +5738,8 @@
         }
 
         private void bindSnoozeAction(RemoteViews big, StandardTemplateParams p) {
-            boolean hideSnoozeButton = mN.isForegroundService() || mN.fullScreenIntent != null
+            boolean hideSnoozeButton = mN.isFgsOrUij()
+                    || mN.fullScreenIntent != null
                     || isBackgroundColorized(p)
                     || p.mViewType != StandardTemplateParams.VIEW_TYPE_BIG;
             big.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton);
@@ -6868,6 +6880,24 @@
     }
 
     /**
+     * @return whether this notification is associated with a user initiated job
+     * @hide
+     */
+    @TestApi
+    public boolean isUserInitiatedJob() {
+        return (flags & Notification.FLAG_USER_INITIATED_JOB) != 0;
+    }
+
+    /**
+     * @return whether this notification is associated with either a foreground service or
+     * a user initiated job
+     * @hide
+     */
+    public boolean isFgsOrUij() {
+        return isForegroundService() || isUserInitiatedJob();
+    }
+
+    /**
      * Describe whether this notification's content such that it should always display
      * immediately when tied to a foreground service, even if the system might generally
      * avoid showing the notifications for short-lived foreground service lifetimes.
@@ -6986,6 +7016,22 @@
     }
 
     /**
+     * @return true for custom notifications, including notifications
+     * with DecoratedCustomViewStyle or DecoratedMediaCustomViewStyle,
+     * and other notifications with user-provided custom views.
+     *
+     * @hide
+     */
+    public Boolean isCustomNotification() {
+        if (contentView == null
+                && bigContentView == null
+                && headsUpContentView == null) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
      * @return true if this notification is showing as a bubble
      *
      * @hide
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 9615b68..746dcb6 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -32,7 +32,6 @@
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
 import android.text.TextUtils;
-import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.Preconditions;
@@ -150,6 +149,10 @@
     private static final String ATT_CONTENT_TYPE = "content_type";
     private static final String ATT_SHOW_BADGE = "show_badge";
     private static final String ATT_USER_LOCKED = "locked";
+    /**
+     * This attribute represents both foreground services and user initiated jobs in U+.
+     * It was not renamed in U on purpose, in order to avoid creating an unnecessary migration path.
+     */
     private static final String ATT_FG_SERVICE_SHOWN = "fgservice";
     private static final String ATT_GROUP = "group";
     private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system";
@@ -249,7 +252,7 @@
     // Bitwise representation of fields that have been changed by the user, preventing the app from
     // making changes to these fields.
     private int mUserLockedFields;
-    private boolean mFgServiceShown;
+    private boolean mUserVisibleTaskShown;
     private boolean mVibrationEnabled;
     private boolean mShowBadge = DEFAULT_SHOW_BADGE;
     private boolean mDeleted = DEFAULT_DELETED;
@@ -317,7 +320,7 @@
             mVibration = Arrays.copyOf(mVibration, MAX_VIBRATION_LENGTH);
         }
         mUserLockedFields = in.readInt();
-        mFgServiceShown = in.readByte() != 0;
+        mUserVisibleTaskShown = in.readByte() != 0;
         mVibrationEnabled = in.readByte() != 0;
         mShowBadge = in.readByte() != 0;
         mDeleted = in.readByte() != 0;
@@ -371,7 +374,7 @@
         dest.writeByte(mLights ? (byte) 1 : (byte) 0);
         dest.writeLongArray(mVibration);
         dest.writeInt(mUserLockedFields);
-        dest.writeByte(mFgServiceShown ? (byte) 1 : (byte) 0);
+        dest.writeByte(mUserVisibleTaskShown ? (byte) 1 : (byte) 0);
         dest.writeByte(mVibrationEnabled ? (byte) 1 : (byte) 0);
         dest.writeByte(mShowBadge ? (byte) 1 : (byte) 0);
         dest.writeByte(mDeleted ? (byte) 1 : (byte) 0);
@@ -418,8 +421,8 @@
      * @hide
      */
     @TestApi
-    public void setFgServiceShown(boolean shown) {
-        mFgServiceShown = shown;
+    public void setUserVisibleTaskShown(boolean shown) {
+        mUserVisibleTaskShown = shown;
     }
 
     /**
@@ -845,8 +848,8 @@
     /**
      * @hide
      */
-    public boolean isFgServiceShown() {
-        return mFgServiceShown;
+    public boolean isUserVisibleTaskShown() {
+        return mUserVisibleTaskShown;
     }
 
     /**
@@ -965,7 +968,7 @@
                 parser, ATT_DELETED_TIME_MS, DEFAULT_DELETION_TIME_MS));
         setGroup(parser.getAttributeValue(null, ATT_GROUP));
         lockFields(safeInt(parser, ATT_USER_LOCKED, 0));
-        setFgServiceShown(safeBool(parser, ATT_FG_SERVICE_SHOWN, false));
+        setUserVisibleTaskShown(safeBool(parser, ATT_FG_SERVICE_SHOWN, false));
         setBlockable(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
         setAllowBubbles(safeInt(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
         setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE));
@@ -1072,8 +1075,8 @@
         if (getUserLockedFields() != 0) {
             out.attributeInt(null, ATT_USER_LOCKED, getUserLockedFields());
         }
-        if (isFgServiceShown()) {
-            out.attributeBoolean(null, ATT_FG_SERVICE_SHOWN, isFgServiceShown());
+        if (isUserVisibleTaskShown()) {
+            out.attributeBoolean(null, ATT_FG_SERVICE_SHOWN, isUserVisibleTaskShown());
         }
         if (canShowBadge()) {
             out.attributeBoolean(null, ATT_SHOW_BADGE, canShowBadge());
@@ -1147,7 +1150,7 @@
         record.put(ATT_LIGHT_COLOR, Integer.toString(getLightColor()));
         record.put(ATT_VIBRATION_ENABLED, Boolean.toString(shouldVibrate()));
         record.put(ATT_USER_LOCKED, Integer.toString(getUserLockedFields()));
-        record.put(ATT_FG_SERVICE_SHOWN, Boolean.toString(isFgServiceShown()));
+        record.put(ATT_FG_SERVICE_SHOWN, Boolean.toString(isUserVisibleTaskShown()));
         record.put(ATT_VIBRATION, longArrayToString(getVibrationPattern()));
         record.put(ATT_SHOW_BADGE, Boolean.toString(canShowBadge()));
         record.put(ATT_DELETED, Boolean.toString(isDeleted()));
@@ -1239,7 +1242,7 @@
                 && mLights == that.mLights
                 && getLightColor() == that.getLightColor()
                 && getUserLockedFields() == that.getUserLockedFields()
-                && isFgServiceShown() == that.isFgServiceShown()
+                && isUserVisibleTaskShown() == that.isUserVisibleTaskShown()
                 && mVibrationEnabled == that.mVibrationEnabled
                 && mShowBadge == that.mShowBadge
                 && isDeleted() == that.isDeleted()
@@ -1265,8 +1268,8 @@
     public int hashCode() {
         int result = Objects.hash(getId(), getName(), mDesc, getImportance(), mBypassDnd,
                 getLockscreenVisibility(), getSound(), mLights, getLightColor(),
-                getUserLockedFields(),
-                isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getDeletedTimeMs(),
+                getUserLockedFields(), isUserVisibleTaskShown(),
+                mVibrationEnabled, mShowBadge, isDeleted(), getDeletedTimeMs(),
                 getGroup(), getAudioAttributes(), isBlockable(), mAllowBubbles,
                 mImportanceLockedDefaultApp, mOriginalImportance,
                 mParentId, mConversationId, mDemoted, mImportantConvo);
@@ -1304,7 +1307,7 @@
                 + ", mLightColor=" + mLightColor
                 + ", mVibration=" + Arrays.toString(mVibration)
                 + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields)
-                + ", mFgServiceShown=" + mFgServiceShown
+                + ", mUserVisibleTaskShown=" + mUserVisibleTaskShown
                 + ", mVibrationEnabled=" + mVibrationEnabled
                 + ", mShowBadge=" + mShowBadge
                 + ", mDeleted=" + mDeleted
@@ -1342,7 +1345,7 @@
             }
         }
         proto.write(NotificationChannelProto.USER_LOCKED_FIELDS, mUserLockedFields);
-        proto.write(NotificationChannelProto.FG_SERVICE_SHOWN, mFgServiceShown);
+        proto.write(NotificationChannelProto.USER_VISIBLE_TASK_SHOWN, mUserVisibleTaskShown);
         proto.write(NotificationChannelProto.IS_VIBRATION_ENABLED, mVibrationEnabled);
         proto.write(NotificationChannelProto.SHOW_BADGE, mShowBadge);
         proto.write(NotificationChannelProto.IS_DELETED, mDeleted);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index d2f2c3c..785470f 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -31,7 +31,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.PermissionChecker;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ShortcutInfo;
 import android.graphics.drawable.Icon;
@@ -726,8 +725,9 @@
      * Cancels a previously posted notification.
      *
      *  <p>If the notification does not currently represent a
-     *  {@link Service#startForeground(int, Notification) foreground service}, it will be
-     *  removed from the UI and live
+     *  {@link Service#startForeground(int, Notification) foreground service} or a
+     *  {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job},
+     *  it will be removed from the UI and live
      *  {@link android.service.notification.NotificationListenerService notification listeners}
      *  will be informed so they can remove the notification from their UIs.</p>
      */
@@ -740,8 +740,9 @@
      * Cancels a previously posted notification.
      *
      *  <p>If the notification does not currently represent a
-     *  {@link Service#startForeground(int, Notification) foreground service}, it will be
-     *  removed from the UI and live
+     *  {@link Service#startForeground(int, Notification) foreground service} or a
+     *  {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job},
+     *  it will be removed from the UI and live
      *  {@link android.service.notification.NotificationListenerService notification listeners}
      *  will be informed so they can remove the notification from their UIs.</p>
      */
@@ -754,8 +755,9 @@
      * Cancels a previously posted notification.
      *
      * <p>If the notification does not currently represent a
-     * {@link Service#startForeground(int, Notification) foreground service}, it will be
-     * removed from the UI and live
+     *  {@link Service#startForeground(int, Notification) foreground service} or a
+     *  {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job},
+     *  it will be removed from the UI and live
      * {@link android.service.notification.NotificationListenerService notification listeners}
      * will be informed so they can remove the notification from their UIs.</p>
      *
@@ -874,19 +876,11 @@
      * {@link android.provider.Settings#ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT}.
      */
     public boolean canUseFullScreenIntent() {
-        final int result = PermissionChecker.checkPermissionForPreflight(mContext,
-                android.Manifest.permission.USE_FULL_SCREEN_INTENT,
-                mContext.getAttributionSource());
-
-        switch (result) {
-            case PermissionChecker.PERMISSION_GRANTED:
-                return true;
-            case PermissionChecker.PERMISSION_SOFT_DENIED:
-            case PermissionChecker.PERMISSION_HARD_DENIED:
-                return false;
-            default:
-                if (localLOGV) Log.v(TAG, "Unknown PermissionChecker result: " + result);
-                return false;
+        INotificationManager service = getService();
+        try {
+            return service.canUseFullScreenIntent(mContext.getAttributionSource());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 9bf56b3..99a7fa2 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -841,7 +841,8 @@
     /**
      * Perform the operation associated with this PendingIntent.
      *
-     * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler)
+     * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String,
+     *          Bundle)
      *
      * @throws CanceledException Throws CanceledException if the PendingIntent
      * is no longer allowing more intents to be sent through it.
@@ -855,7 +856,8 @@
      *
      * @param code Result code to supply back to the PendingIntent's target.
      *
-     * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler)
+     * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String,
+     *          Bundle)
      *
      * @throws CanceledException Throws CanceledException if the PendingIntent
      * is no longer allowing more intents to be sent through it.
@@ -875,7 +877,8 @@
      * original Intent. If flag {@link #FLAG_IMMUTABLE} was set when this
      * pending intent was created, this argument will be ignored.
      *
-     * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler)
+     * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String,
+     *          Bundle)
      *
      * @throws CanceledException Throws CanceledException if the PendingIntent
      * is no longer allowing more intents to be sent through it.
@@ -892,6 +895,11 @@
      * @param options Additional options the caller would like to provide to modify the
      * sending behavior.  May be built from an {@link ActivityOptions} to apply to an
      * activity start.
+     *
+     * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String)
+     *
+     * @throws CanceledException Throws CanceledException if the PendingIntent
+     * is no longer allowing more intents to be sent through it.
      */
     public void send(@Nullable Bundle options) throws CanceledException {
         send(null, 0, null, null, null, null, options);
@@ -908,7 +916,8 @@
      * should happen.  If null, the callback will happen from the thread
      * pool of the process.
      *
-     * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler)
+     * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String,
+     *          Bundle)
      *
      * @throws CanceledException Throws CanceledException if the PendingIntent
      * is no longer allowing more intents to be sent through it.
@@ -942,11 +951,8 @@
      * should happen.  If null, the callback will happen from the thread
      * pool of the process.
      *
-     * @see #send()
-     * @see #send(int)
-     * @see #send(Context, int, Intent)
-     * @see #send(int, android.app.PendingIntent.OnFinished, Handler)
-     * @see #send(Context, int, Intent, OnFinished, Handler, String)
+     * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String,
+     *          Bundle)
      *
      * @throws CanceledException Throws CanceledException if the PendingIntent
      * is no longer allowing more intents to be sent through it.
@@ -985,11 +991,8 @@
      * {@link Context#sendBroadcast(Intent, String) Context.sendOrderedBroadcast(Intent, String)}.
      * If null, no permission is required.
      *
-     * @see #send()
-     * @see #send(int)
-     * @see #send(Context, int, Intent)
-     * @see #send(int, android.app.PendingIntent.OnFinished, Handler)
-     * @see #send(Context, int, Intent, OnFinished, Handler)
+     * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String,
+     *          Bundle)
      *
      * @throws CanceledException Throws CanceledException if the PendingIntent
      * is no longer allowing more intents to be sent through it.
@@ -1032,12 +1035,6 @@
      * @param options Additional options the caller would like to provide to modify the sending
      * behavior.  May be built from an {@link ActivityOptions} to apply to an activity start.
      *
-     * @see #send()
-     * @see #send(int)
-     * @see #send(Context, int, Intent)
-     * @see #send(int, android.app.PendingIntent.OnFinished, Handler)
-     * @see #send(Context, int, Intent, OnFinished, Handler)
-     *
      * @throws CanceledException Throws CanceledException if the PendingIntent
      * is no longer allowing more intents to be sent through it.
      */
diff --git a/core/java/android/app/RemoteServiceException.java b/core/java/android/app/RemoteServiceException.java
index 620adbe..c5ad110 100644
--- a/core/java/android/app/RemoteServiceException.java
+++ b/core/java/android/app/RemoteServiceException.java
@@ -102,6 +102,21 @@
     }
 
     /**
+     * Exception used to crash an app process when the system finds an error in a user-initiated job
+     * notification.
+     *
+     * @hide
+     */
+    public static class BadUserInitiatedJobNotificationException extends RemoteServiceException {
+        /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
+        public static final int TYPE_ID = 6;
+
+        public BadUserInitiatedJobNotificationException(String msg) {
+            super(msg);
+        }
+    }
+
+    /**
      * Exception used to crash an app process when it calls a setting activity that requires
      * the {@code REQUEST_PASSWORD_COMPLEXITY} permission.
      *
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 86f6a93..658e084 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -46,6 +46,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.ArraySet;
+import android.util.DebugUtils;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Display;
@@ -111,6 +112,7 @@
     private static final String LOG_TAG = UiAutomation.class.getSimpleName();
 
     private static final boolean DEBUG = false;
+    private static final boolean VERBOSE = false;
 
     private static final int CONNECTION_ID_UNDEFINED = -1;
 
@@ -321,11 +323,18 @@
      * @hide
      */
     public void connectWithTimeout(int flags, long timeoutMillis) throws TimeoutException {
+        if (DEBUG) {
+            Log.d(LOG_TAG, "connectWithTimeout: user=" + Process.myUserHandle().getIdentifier()
+                    + ", flags=" + DebugUtils.flagsToString(UiAutomation.class, "FLAG_", flags)
+                    + ", timeout=" + timeoutMillis + "ms");
+        }
         synchronized (mLock) {
             throwIfConnectedLocked();
             if (mConnectionState == ConnectionState.CONNECTING) {
+                if (DEBUG) Log.d(LOG_TAG, "already connecting");
                 return;
             }
+            if (DEBUG) Log.d(LOG_TAG, "setting state to CONNECTING");
             mConnectionState = ConnectionState.CONNECTING;
             mRemoteCallbackThread = new HandlerThread("UiAutomation");
             mRemoteCallbackThread.start();
@@ -341,6 +350,7 @@
             // If UiAutomation is not allowed to use the accessibility subsystem, the
             // connection state should keep disconnected and not to start the client connection.
             if (!useAccessibility()) {
+                if (DEBUG) Log.d(LOG_TAG, "setting state to DISCONNECTED");
                 mConnectionState = ConnectionState.DISCONNECTED;
                 return;
             }
@@ -357,6 +367,7 @@
                 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
                 final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
                 if (remainingTimeMillis <= 0) {
+                    if (DEBUG) Log.d(LOG_TAG, "setting state to FAILED");
                     mConnectionState = ConnectionState.FAILED;
                     throw new TimeoutException("Timeout while connecting " + this);
                 }
@@ -1367,7 +1378,8 @@
             UserHandle userHandle) {
         try {
             if (DEBUG) {
-                Log.i(LOG_TAG, "Granting runtime permission");
+                Log.i(LOG_TAG, "Granting runtime permission (" + permission + ") to package "
+                        + packageName + " on user " + userHandle);
             }
             // Calling out without a lock held.
             mUiAutomationConnection.grantRuntimePermission(packageName,
@@ -1592,7 +1604,7 @@
     private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
 
         public IAccessibilityServiceClientImpl(Looper looper, int generationId) {
-            super(null, looper, new Callbacks() {
+            super(/* context= */ null, looper, new Callbacks() {
                 private final int mGenerationId = generationId;
 
                 /**
@@ -1606,10 +1618,21 @@
 
                 @Override
                 public void init(int connectionId, IBinder windowToken) {
+                    if (DEBUG) {
+                        Log.d(LOG_TAG, "init(): connectionId=" + connectionId + ", windowToken="
+                                + windowToken + ", user=" + Process.myUserHandle()
+                                + ", mGenerationId=" + mGenerationId
+                                + ", UiAutomation.mGenerationId="
+                                + UiAutomation.this.mGenerationId);
+                    }
                     synchronized (mLock) {
                         if (isGenerationChangedLocked()) {
+                            if (DEBUG) {
+                                Log.d(LOG_TAG, "init(): returning because generation id changed");
+                            }
                             return;
                         }
+                        if (DEBUG) Log.d(LOG_TAG, "setting state to CONNECTED");
                         mConnectionState = ConnectionState.CONNECTED;
                         mConnectionId = connectionId;
                         mLock.notifyAll();
@@ -1662,9 +1685,20 @@
 
                 @Override
                 public void onAccessibilityEvent(AccessibilityEvent event) {
+                    if (VERBOSE) {
+                        Log.v(LOG_TAG, "onAccessibilityEvent(" + Process.myUserHandle() + "): "
+                                + event);
+                    }
+
                     final OnAccessibilityEventListener listener;
                     synchronized (mLock) {
                         if (isGenerationChangedLocked()) {
+                            if (VERBOSE) {
+                                Log.v(LOG_TAG, "onAccessibilityEvent(): returning because "
+                                        + "generation id changed (from "
+                                        + UiAutomation.this.mGenerationId + " to "
+                                        + mGenerationId + ")");
+                            }
                             return;
                         }
                         // It is not guaranteed that the accessibility framework sends events by the
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 3a32f23..13e800e 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -103,6 +103,7 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public UiAutomationConnection() {
+        Log.d(TAG, "Created on user " + Process.myUserHandle());
     }
 
     @Override
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7dabe60..4d3338b 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9634,7 +9634,8 @@
      * @see #isProfileOwnerApp
      * @see #isDeviceOwnerApp
      * @param admin Which {@link DeviceAdminReceiver} this request is associate with.
-     * @param profileName The name of the profile.
+     * @param profileName The name of the profile. If the name is longer than 200 characters
+     *                    it will be truncated.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
      */
     public void setProfileName(@NonNull ComponentName admin, String profileName) {
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index 593f736..052f670 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -1802,14 +1802,6 @@
                     PREFIX + "UNLAUNCHABLE_APP_WORK_PAUSED_TITLE";
 
             /**
-             * Text for dialog shown when user tries to open a work app when the work profile is
-             * turned off, confirming that the user wants to turn on access to their
-             * work apps.
-             */
-            public static final String UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE =
-                    PREFIX + "UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE";
-
-            /**
              * Notification title shown when work profile is credential encrypted and requires
              * the user to unlock before it's usable.
              */
diff --git a/core/java/android/app/admin/IntentFilterPolicyKey.java b/core/java/android/app/admin/IntentFilterPolicyKey.java
index 30aad96..7526a7b 100644
--- a/core/java/android/app/admin/IntentFilterPolicyKey.java
+++ b/core/java/android/app/admin/IntentFilterPolicyKey.java
@@ -28,7 +28,9 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Log;
 
+import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
@@ -45,6 +47,10 @@
  */
 @SystemApi
 public final class IntentFilterPolicyKey extends PolicyKey {
+
+    private static final String TAG = "IntentFilterPolicyKey";
+
+    private static final String TAG_INTENT_FILTER_ENTRY = "filter";
     private final IntentFilter mFilter;
 
     /**
@@ -83,7 +89,9 @@
     @Override
     public void saveToXml(TypedXmlSerializer serializer) throws IOException {
         serializer.attribute(/* namespace= */ null, ATTR_POLICY_IDENTIFIER, getIdentifier());
+        serializer.startTag(/* namespace= */ null, TAG_INTENT_FILTER_ENTRY);
         mFilter.writeToXml(serializer);
+        serializer.endTag(/* namespace= */ null, TAG_INTENT_FILTER_ENTRY);
     }
 
     /**
@@ -93,11 +101,27 @@
     public IntentFilterPolicyKey readFromXml(TypedXmlPullParser parser)
             throws XmlPullParserException, IOException {
         String identifier = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_IDENTIFIER);
-        IntentFilter filter = new IntentFilter();
-        filter.readFromXml(parser);
+        IntentFilter filter = readIntentFilterFromXml(parser);
         return new IntentFilterPolicyKey(identifier, filter);
     }
 
+    @Nullable
+    private IntentFilter readIntentFilterFromXml(TypedXmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            String tag = parser.getName();
+            if (tag.equals(TAG_INTENT_FILTER_ENTRY)) {
+                IntentFilter filter = new IntentFilter();
+                filter.readFromXml(parser);
+                return filter;
+            }
+            Log.e(TAG, "Unknown tag: " + tag);
+        }
+        Log.e(TAG, "Error parsing IntentFilterPolicyKey, IntentFilter not found");
+        return null;
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/android/app/search/SearchTarget.java b/core/java/android/app/search/SearchTarget.java
index a3874f7..8132b81 100644
--- a/core/java/android/app/search/SearchTarget.java
+++ b/core/java/android/app/search/SearchTarget.java
@@ -186,16 +186,6 @@
         mAppWidgetProviderInfo = appWidgetProviderInfo;
         mSliceUri = sliceUri;
         mExtras = extras != null ? extras : new Bundle();
-
-        int published = 0;
-        if (mSearchAction != null) published++;
-        if (mShortcutInfo != null) published++;
-        if (mAppWidgetProviderInfo != null) published++;
-        if (mSliceUri != null) published++;
-        if (published > 1) {
-            throw new IllegalStateException("Only one of SearchAction, ShortcutInfo,"
-                    + " AppWidgetProviderInfo, SliceUri can be assigned in a SearchTarget.");
-        }
     }
 
     /**
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 08f345c..b89d3fe 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -36,6 +36,8 @@
         in String callingPackage, int userId);
 
     List<AssociationInfo> getAssociations(String callingPackage, int userId);
+
+    @EnforcePermission("MANAGE_COMPANION_DEVICES")
     List<AssociationInfo> getAllAssociationsForUser(int userId);
 
     /** @deprecated */
@@ -48,26 +50,28 @@
 
     PendingIntent requestNotificationAccess(in ComponentName component, int userId);
 
-    /** @deprecated */
     @EnforcePermission("MANAGE_COMPANION_DEVICES")
     boolean isDeviceAssociatedForWifiConnection(in String packageName, in String macAddress,
         int userId);
 
+    @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
     void registerDevicePresenceListenerService(in String deviceAddress, in String callingPackage,
         int userId);
 
+    @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
     void unregisterDevicePresenceListenerService(in String deviceAddress, in String callingPackage,
         int userId);
 
-    /** @deprecated */
     boolean canPairWithoutPrompt(in String packageName, in String deviceMacAddress, int userId);
 
-    /** @deprecated */
+    @EnforcePermission("ASSOCIATE_COMPANION_DEVICES")
     void createAssociation(in String packageName, in String macAddress, int userId,
         in byte[] certificate);
 
+    @EnforcePermission("MANAGE_COMPANION_DEVICES")
     void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
 
+    @EnforcePermission("MANAGE_COMPANION_DEVICES")
     void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
 
     void addOnTransportsChangedListener(IOnTransportsChangedListener listener);
@@ -90,8 +94,10 @@
     void startSystemDataTransfer(String packageName, int userId, int associationId,
         in ISystemDataTransferCallback callback);
 
+    @EnforcePermission("DELIVER_COMPANION_MESSAGES")
     void attachSystemDataTransport(String packageName, int userId, int associationId, in ParcelFileDescriptor fd);
 
+    @EnforcePermission("DELIVER_COMPANION_MESSAGES")
     void detachSystemDataTransport(String packageName, int userId, int associationId);
 
     boolean isCompanionApplicationBound(String packageName, int userId);
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 81fc029..23ba336 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -261,14 +261,17 @@
     public boolean guestToRemove;
 
     /**
-     * This is used to optimize the creation of an user, i.e. OEMs might choose to pre-create a
+     * This is used to optimize the creation of a user, i.e. OEMs might choose to pre-create a
      * number of users at the first boot, so the actual creation later is faster.
      *
      * <p>A {@code preCreated} user is not a real user yet, so it should not show up on regular
      * user operations (other than user creation per se).
      *
-     * <p>Once the pre-created is used to create a "real" user later on, {@code preCreate} is set to
-     * {@code false}.
+     * <p>Once the pre-created is used to create a "real" user later on, {@code preCreated} is set
+     * to {@code false}.
+     *
+     * <p><b>NOTE: Pre-created users are deprecated. This field remains to be able to recognize
+     * pre-created users in older versions, but will eventually be removed.
      */
     public boolean preCreated;
 
@@ -277,6 +280,9 @@
      * user.
      *
      * <p><b>NOTE: </b>only used for debugging purposes, it's not set when marshalled to a parcel.
+     *
+     * <p><b>NOTE: Pre-created users are deprecated. This field remains to be able to recognize
+     * pre-created users in older versions, but will eventually ve removed.
      */
     public boolean convertedFromPreCreated;
 
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index ac65933..a238a41d 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -16,17 +16,29 @@
 
 package android.content.res;
 
+import static android.system.OsConstants.S_ISFIFO;
+import static android.system.OsConstants.S_ISSOCK;
+
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
 
 import java.io.Closeable;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
 
 /**
  * File descriptor of an entry in the AssetManager.  This provides your own
@@ -200,13 +212,88 @@
      * An InputStream you can create on a ParcelFileDescriptor, which will
      * take care of calling {@link ParcelFileDescriptor#close
      * ParcelFileDescriptor.close()} for you when the stream is closed.
+     * It has a ParcelFileDescriptor.AutoCloseInputStream member to make delegate calls
+     * and during definition it will create seekable or non seekable child object
+     * AssetFileDescriptor.AutoCloseInputStream depends on the type of file descriptor
+     * to provide different solution.
      */
     public static class AutoCloseInputStream
             extends ParcelFileDescriptor.AutoCloseInputStream {
-        private long mRemaining;
+        private ParcelFileDescriptor.AutoCloseInputStream mDelegateInputStream;
 
         public AutoCloseInputStream(AssetFileDescriptor fd) throws IOException {
             super(fd.getParcelFileDescriptor());
+            StructStat ss;
+            try {
+                ss = Os.fstat(fd.getParcelFileDescriptor().getFileDescriptor());
+            } catch (ErrnoException e) {
+                throw new IOException(e);
+            }
+            if (S_ISSOCK(ss.st_mode) || S_ISFIFO(ss.st_mode)) {
+                mDelegateInputStream = new NonSeekableAutoCloseInputStream(fd);
+            } else {
+                mDelegateInputStream = new SeekableAutoCloseInputStream(fd);
+            }
+        }
+
+        @Override
+        public int available() throws IOException {
+            return mDelegateInputStream.available();
+        }
+
+        @Override
+        public int read() throws IOException {
+            return mDelegateInputStream.read();
+        }
+
+        @Override
+        public int read(byte[] buffer, int offset, int count) throws IOException {
+            return mDelegateInputStream.read(buffer, offset, count);
+        }
+
+        @Override
+        public int read(byte[] buffer) throws IOException {
+            return mDelegateInputStream.read(buffer);
+        }
+
+        @Override
+        public long skip(long count) throws IOException {
+            return mDelegateInputStream.skip(count);
+        }
+
+        @Override
+        public void mark(int readlimit) {
+            mDelegateInputStream.mark(readlimit);
+        }
+
+        @Override
+        public boolean markSupported() {
+            return mDelegateInputStream.markSupported();
+        }
+
+        @Override
+        public synchronized void reset() throws IOException {
+            mDelegateInputStream.reset();
+        }
+
+        @Override
+        public FileChannel getChannel() {
+            return mDelegateInputStream.getChannel();
+        }
+    }
+
+    /**
+     * An InputStream you can create on a non seekable file descriptor,
+     * like PIPE, SOCKET and FIFO, which will take care of calling
+     * {@link ParcelFileDescriptor#close ParcelFileDescriptor.close()}
+     * for you when the stream is closed.
+     */
+    private static class NonSeekableAutoCloseInputStream
+            extends ParcelFileDescriptor.AutoCloseInputStream {
+        private long mRemaining;
+
+        NonSeekableAutoCloseInputStream(AssetFileDescriptor fd) throws IOException {
+            super(fd.getParcelFileDescriptor());
             super.skip(fd.getStartOffset());
             mRemaining = (int) fd.getLength();
         }
@@ -284,6 +371,254 @@
     }
 
     /**
+     * An InputStream you can create on a seekable file descriptor, which means
+     * you can use pread to read from a specific offset, this will take care of
+     * calling {@link ParcelFileDescriptor#close ParcelFileDescriptor.close()}
+     * for you when the stream is closed.
+     */
+    private static class SeekableAutoCloseInputStream
+            extends ParcelFileDescriptor.AutoCloseInputStream {
+        /** Size of current file. */
+        private long mTotalSize;
+        /** The absolute position of current file start point. */
+        private final long mFileOffset;
+        /** The relative position where input stream is against mFileOffset. */
+        private long mOffset;
+        private OffsetCorrectFileChannel mOffsetCorrectFileChannel;
+
+        SeekableAutoCloseInputStream(AssetFileDescriptor fd) throws IOException {
+            super(fd.getParcelFileDescriptor());
+            mTotalSize = fd.getLength();
+            mFileOffset = fd.getStartOffset();
+        }
+
+        @Override
+        public int available() throws IOException {
+            long available = mTotalSize - mOffset;
+            return available >= 0
+                    ? (available < 0x7fffffff ? (int) available : 0x7fffffff)
+                    : 0;
+        }
+
+        @Override
+        public int read() throws IOException {
+            byte[] buffer = new byte[1];
+            int result = read(buffer, 0, 1);
+            return result == -1 ? -1 : buffer[0] & 0xff;
+        }
+
+        @Override
+        public int read(byte[] buffer, int offset, int count) throws IOException {
+            int available = available();
+            if (available <= 0) {
+                return -1;
+            }
+
+            if (count > available) count = available;
+            try {
+                int res = Os.pread(getFD(), buffer, offset, count, mFileOffset + mOffset);
+                // pread returns 0 at end of file, while java's InputStream interface requires -1
+                if (res == 0) res = -1;
+                if (res > 0) {
+                    mOffset += res;
+                    updateChannelPosition(mOffset + mFileOffset);
+                }
+                return res;
+            } catch (ErrnoException e) {
+                throw new IOException(e);
+            }
+        }
+
+        @Override
+        public int read(byte[] buffer) throws IOException {
+            return read(buffer, 0, buffer.length);
+        }
+
+        @Override
+        public long skip(long count) throws IOException {
+            int available = available();
+            if (available <= 0) {
+                return -1;
+            }
+
+            if (count > available) count = available;
+            mOffset += count;
+            updateChannelPosition(mOffset + mFileOffset);
+            return count;
+        }
+
+        @Override
+        public void mark(int readlimit) {
+            // Not supported.
+            return;
+        }
+
+        @Override
+        public boolean markSupported() {
+            return false;
+        }
+
+        @Override
+        public synchronized void reset() throws IOException {
+            // Not supported.
+            return;
+        }
+
+        @Override
+        public FileChannel getChannel() {
+            if (mOffsetCorrectFileChannel == null) {
+                mOffsetCorrectFileChannel = new OffsetCorrectFileChannel(super.getChannel());
+            }
+            try {
+                updateChannelPosition(mOffset + mFileOffset);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            return mOffsetCorrectFileChannel;
+        }
+
+        /**
+         * Update the position of mOffsetCorrectFileChannel only after it is constructed.
+         *
+         * @param newPosition The absolute position mOffsetCorrectFileChannel needs to be moved to.
+         */
+        private void updateChannelPosition(long newPosition) throws IOException {
+            if (mOffsetCorrectFileChannel != null) {
+                mOffsetCorrectFileChannel.position(newPosition);
+            }
+        }
+
+        /**
+         * A FileChannel wrapper that will update mOffset of the AutoCloseInputStream
+         * to correct position when using FileChannel to read. All occurrence of position
+         * should be using absolute solution and each override method just do Delegation
+         * besides additional check. All methods related to write mode have been disabled
+         * and will throw UnsupportedOperationException with customized message.
+         */
+        private class OffsetCorrectFileChannel extends FileChannel {
+            private final FileChannel mDelegate;
+            private static final String METHOD_NOT_SUPPORTED_MESSAGE =
+                    "This Method is not supported in AutoCloseInputStream FileChannel.";
+
+            OffsetCorrectFileChannel(FileChannel fc) {
+                mDelegate = fc;
+            }
+
+            @Override
+            public int read(ByteBuffer dst) throws IOException {
+                if (available() <= 0) return -1;
+                int bytesRead = mDelegate.read(dst);
+                if (bytesRead != -1) mOffset += bytesRead;
+                return bytesRead;
+            }
+
+            @Override
+            public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
+                if (available() <= 0) return -1;
+                if (mOffset + length > mTotalSize) {
+                    length = (int) (mTotalSize - mOffset);
+                }
+                long bytesRead = mDelegate.read(dsts, offset, length);
+                if (bytesRead != -1) mOffset += bytesRead;
+                return bytesRead;
+            }
+
+            @Override
+            /**The only read method that does not move channel position*/
+            public int read(ByteBuffer dst, long position) throws IOException {
+                if (position - mFileOffset > mTotalSize) return -1;
+                return mDelegate.read(dst, position);
+            }
+
+            @Override
+            public long position() throws IOException {
+                return mDelegate.position();
+            }
+
+            @Override
+            public FileChannel position(long newPosition) throws IOException {
+                mOffset = newPosition - mFileOffset;
+                return mDelegate.position(newPosition);
+            }
+
+            @Override
+            public long size() throws IOException {
+                return mTotalSize;
+            }
+
+            @Override
+            public long transferTo(long position, long count, WritableByteChannel target)
+                    throws IOException {
+                if (position - mFileOffset > mTotalSize) {
+                    return 0;
+                }
+                if (position - mFileOffset + count > mTotalSize) {
+                    count = mTotalSize - (position - mFileOffset);
+                }
+                return mDelegate.transferTo(position, count, target);
+            }
+
+            @Override
+            public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
+                if (position - mFileOffset > mTotalSize) {
+                    throw new IOException(
+                            "Cannot map to buffer because position exceed current file size.");
+                }
+                if (position - mFileOffset + size > mTotalSize) {
+                    size = mTotalSize - (position - mFileOffset);
+                }
+                return mDelegate.map(mode, position, size);
+            }
+
+            @Override
+            protected void implCloseChannel() throws IOException {
+                mDelegate.close();
+            }
+
+            @Override
+            public int write(ByteBuffer src) throws IOException {
+                throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
+            }
+
+            @Override
+            public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
+                throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
+            }
+
+            @Override
+            public int write(ByteBuffer src, long position) throws IOException {
+                throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
+            }
+
+            @Override
+            public long transferFrom(ReadableByteChannel src, long position, long count)
+                    throws IOException {
+                throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
+            }
+
+            @Override
+            public FileChannel truncate(long size) throws IOException {
+                throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
+            }
+
+            @Override
+            public void force(boolean metaData) throws IOException {
+                throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
+            }
+
+            @Override
+            public FileLock lock(long position, long size, boolean shared) throws IOException {
+                throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
+            }
+
+            @Override
+            public FileLock tryLock(long position, long size, boolean shared) throws IOException {
+                throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
+            }
+        }
+    }
+
+    /**
      * An OutputStream you can create on a ParcelFileDescriptor, which will
      * take care of calling {@link ParcelFileDescriptor#close
      * ParcelFileDescriptor.close()} for you when the stream is closed.
diff --git a/core/java/android/credentials/CredentialOption.java b/core/java/android/credentials/CredentialOption.java
index e933123..df948f17 100644
--- a/core/java/android/credentials/CredentialOption.java
+++ b/core/java/android/credentials/CredentialOption.java
@@ -37,8 +37,7 @@
 
 /**
  * Information about a specific type of credential to be requested during a {@link
- * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor,
- * OutcomeReceiver)} operation.
+ * CredentialManager#getCredential} operation.
  */
 public final class CredentialOption implements Parcelable {
 
@@ -196,9 +195,8 @@
      * @throws NullPointerException If {@code credentialRetrievalData}, or
      * {@code candidateQueryData} is null.
      *
-     * @deprecated replaced by Builder
+     * @hide
      */
-    @Deprecated
     public CredentialOption(
             @NonNull String type,
             @NonNull Bundle credentialRetrievalData,
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index fa678fc..2e40f60 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -142,6 +142,7 @@
         private PromptInfo mPromptInfo;
         private ButtonInfo mNegativeButtonInfo;
         private Context mContext;
+        private IAuthService mService;
 
         /**
          * Creates a builder for a {@link BiometricPrompt} dialog.
@@ -212,6 +213,18 @@
         }
 
         /**
+         * @param service
+         * @return This builder.
+         * @hide
+         */
+        @RequiresPermission(TEST_BIOMETRIC)
+        @NonNull
+        public Builder setService(@NonNull IAuthService service) {
+            mService = service;
+            return this;
+        }
+
+        /**
          * Sets an optional title, subtitle, and/or description that will override other text when
          * the user is authenticating with PIN/pattern/password. Currently for internal use only.
          * @return This builder.
@@ -472,7 +485,9 @@
                 throw new IllegalArgumentException("Can't have both negative button behavior"
                         + " and device credential enabled");
             }
-            return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo);
+            mService = (mService == null) ? IAuthService.Stub.asInterface(
+                    ServiceManager.getService(Context.AUTH_SERVICE)) : mService;
+            return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo, mService);
         }
     }
 
@@ -521,7 +536,6 @@
         public void onAuthenticationFailed() {
             mExecutor.execute(() -> {
                 mAuthenticationCallback.onAuthenticationFailed();
-                mIsPromptShowing = false;
             });
         }
 
@@ -604,12 +618,12 @@
 
     private boolean mIsPromptShowing;
 
-    private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo) {
+    private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo,
+            IAuthService service) {
         mContext = context;
         mPromptInfo = promptInfo;
         mNegativeButtonInfo = negativeButtonInfo;
-        mService = IAuthService.Stub.asInterface(
-                ServiceManager.getService(Context.AUTH_SERVICE));
+        mService = service;
         mIsPromptShowing = false;
     }
 
diff --git a/core/java/android/hardware/biometrics/ComponentInfoInternal.java b/core/java/android/hardware/biometrics/ComponentInfoInternal.java
index 3b61a56..2e708de 100644
--- a/core/java/android/hardware/biometrics/ComponentInfoInternal.java
+++ b/core/java/android/hardware/biometrics/ComponentInfoInternal.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
 
 /**
  * The internal class for storing the component info for a subsystem of the biometric sensor,
@@ -90,12 +92,19 @@
         dest.writeString(softwareVersion);
     }
 
-    @Override
-    public String toString() {
-        return "ComponentId: " + componentId
-                + ", HardwareVersion: " + hardwareVersion
-                + ", FirmwareVersion: " + firmwareVersion
-                + ", SerialNumber " + serialNumber
-                + ", SoftwareVersion: " + softwareVersion;
+    /**
+     * Print the component info into the given stream.
+     *
+     * @param pw The stream to dump the info into.
+     * @hide
+     */
+    public void dump(@NonNull IndentingPrintWriter pw) {
+        pw.println(TextUtils.formatSimple("componentId: %s", componentId));
+        pw.increaseIndent();
+        pw.println(TextUtils.formatSimple("hardwareVersion: %s", hardwareVersion));
+        pw.println(TextUtils.formatSimple("firmwareVersion: %s", firmwareVersion));
+        pw.println(TextUtils.formatSimple("serialNumber: %s", serialNumber));
+        pw.println(TextUtils.formatSimple("softwareVersion: %s", softwareVersion));
+        pw.decreaseIndent();
     }
 }
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index c88af5a..1a38c88 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -58,10 +58,10 @@
     boolean hasEnrolledBiometrics(int userId, String opPackageName);
 
     // Registers an authenticator (e.g. face, fingerprint, iris).
-    // Id must be unique, whereas strength and modality don't need to be.
+    // Sensor Id in sensor props must be unique, whereas modality doesn't need to be.
     // TODO(b/123321528): Turn strength and modality into enums.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void registerAuthenticator(int id, int modality, int strength,
+    void registerAuthenticator(int modality, in SensorPropertiesInternal props,
             IBiometricAuthenticator authenticator);
 
     // Register callback for when keyguard biometric eligibility changes.
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index fa16e16..6d43ddf 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1051,6 +1051,29 @@
             return "ModelParamRange [start=" + mStart + ", end=" + mEnd + "]";
         }
     }
+    /**
+     * SoundTrigger model parameter types.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "MODEL_PARAM" }, value = {
+            MODEL_PARAM_INVALID,
+            MODEL_PARAM_THRESHOLD_FACTOR
+    })
+    public @interface ModelParamTypes {}
+
+    /**
+     * See {@link ModelParams.INVALID}
+     * @hide
+     */
+    @TestApi
+    public static final int MODEL_PARAM_INVALID = ModelParams.INVALID;
+    /**
+     * See {@link ModelParams.THRESHOLD_FACTOR}
+     * @hide
+     */
+    @TestApi
+    public static final int MODEL_PARAM_THRESHOLD_FACTOR = ModelParams.THRESHOLD_FACTOR;
 
     /**
      * Modes for key phrase recognition
@@ -1450,7 +1473,8 @@
      *
      *  @hide
      */
-    public static class RecognitionConfig implements Parcelable {
+    @TestApi
+    public static final class RecognitionConfig implements Parcelable {
         /** True if the DSP should capture the trigger sound and make it available for further
          * capture. */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1464,6 +1488,7 @@
          * options for each keyphrase. */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         @NonNull
+        @SuppressLint("ArrayReturn")
         public final KeyphraseRecognitionExtra keyphrases[];
         /** Opaque data for use by system applications who know about voice engine internals,
          * typically during enrollment. */
@@ -1479,8 +1504,8 @@
         public final int audioCapabilities;
 
         public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
-                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
-                int audioCapabilities) {
+                @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
+                @Nullable byte[] data, int audioCapabilities) {
             this.captureRequested = captureRequested;
             this.allowMultipleTriggers = allowMultipleTriggers;
             this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
@@ -1490,7 +1515,8 @@
 
         @UnsupportedAppUsage
         public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
-                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+                @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
+                @Nullable byte[] data) {
             this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
         }
 
@@ -1517,7 +1543,7 @@
         }
 
         @Override
-        public void writeToParcel(Parcel dest, int flags) {
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeByte((byte) (captureRequested ? 1 : 0));
             dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0));
             dest.writeTypedArray(keyphrases, flags);
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 2e05b8d..eb471705 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1222,7 +1222,7 @@
         /**
          * Upside Down Cake.
          */
-        public static final int UPSIDE_DOWN_CAKE = CUR_DEVELOPMENT;
+        public static final int UPSIDE_DOWN_CAKE = 34;
 
         /**
          * Vanilla Ice Cream.
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 7337b37..e81475b 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3717,17 +3717,24 @@
      * {@link android.Manifest.permission#CREATE_USERS} suffices if flags are in
      * com.android.server.pm.UserManagerService#ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION.
      *
+     *
      * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}.
      * @return the {@link UserInfo} object for the created user.
      *
      * @throws UserOperationException if the user could not be created.
+     *
+     * @deprecated Pre-created users are deprecated. This method should no longer be used, and will
+     *             be removed once all the callers are removed.
+     *
      * @hide
      */
+    @Deprecated
     @TestApi
     @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
             Manifest.permission.CREATE_USERS})
     public @NonNull UserInfo preCreateUser(@NonNull String userType)
             throws UserOperationException {
+        Log.w(TAG, "preCreateUser(): Pre-created user is deprecated.");
         try {
             return mService.preCreateUserWithThrow(userType);
         } catch (ServiceSpecificException e) {
@@ -4307,8 +4314,12 @@
     /**
      * Returns information for all users on this device, based on the filtering parameters.
      *
+     * @deprecated Pre-created users are deprecated and no longer supported.
+     *             Use {@link #getUsers()}, {@link #getUsers(boolean)}, or {@link #getAliveUsers()}
+     *             instead.
      * @hide
      */
+    @Deprecated
     @TestApi
     @RequiresPermission(anyOf = {
             android.Manifest.permission.MANAGE_USERS,
diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS
index d34b45b..4603e43f 100644
--- a/core/java/android/permission/OWNERS
+++ b/core/java/android/permission/OWNERS
@@ -1,18 +1,19 @@
 # Bug component: 137825
 
-evanseverson@google.com
-evanxinchen@google.com
 ashfall@google.com
-guojing@google.com
+augale@google.com
+evanseverson@google.com
+fayey@google.com
 jaysullivan@google.com
+joecastro@google.com
 kvakil@google.com
 mrulhania@google.com
 narayan@google.com
 ntmyren@google.com
 olekarg@google.com
 pyuli@google.com
-raphk@google.com
 rmacgregor@google.com
 sergeynv@google.com
 theianchen@google.com
+yutingfang@google.com
 zhanghai@google.com
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ee91901..2db09dd 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3449,7 +3449,7 @@
                                     + " type:" + mUri.getPath()
                                     + " in package:" + cr.getPackageName());
                         }
-                        for (int i = 0; i < mValues.size(); ++i) {
+                        for (int i = mValues.size() - 1; i >= 0; i--) {
                             String key = mValues.keyAt(i);
                             if (key.startsWith(prefix)) {
                                 mValues.remove(key);
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index b977606..e87333f 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -231,12 +231,18 @@
     }
 
     private final ICredentialProviderService mInterface = new ICredentialProviderService.Stub() {
-        public ICancellationSignal onBeginGetCredential(BeginGetCredentialRequest request,
+        @Override
+        public void onBeginGetCredential(BeginGetCredentialRequest request,
                 IBeginGetCredentialCallback callback) {
             Objects.requireNonNull(request);
             Objects.requireNonNull(callback);
 
             ICancellationSignal transport = CancellationSignal.createTransport();
+            try {
+                callback.onCancellable(transport);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
 
             mHandler.sendMessage(obtainMessage(
                     CredentialProviderService::onBeginGetCredential,
@@ -267,7 +273,6 @@
                         }
                     }
             ));
-            return transport;
         }
         private void enforceRemoteEntryPermission() {
             String permission =
@@ -280,12 +285,17 @@
         }
 
         @Override
-        public ICancellationSignal onBeginCreateCredential(BeginCreateCredentialRequest request,
+        public void onBeginCreateCredential(BeginCreateCredentialRequest request,
                 IBeginCreateCredentialCallback callback) {
             Objects.requireNonNull(request);
             Objects.requireNonNull(callback);
 
             ICancellationSignal transport = CancellationSignal.createTransport();
+            try {
+                callback.onCancellable(transport);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
 
             mHandler.sendMessage(obtainMessage(
                     CredentialProviderService::onBeginCreateCredential,
@@ -316,16 +326,20 @@
                         }
                     }
             ));
-            return transport;
         }
 
         @Override
-        public ICancellationSignal onClearCredentialState(ClearCredentialStateRequest request,
+        public void onClearCredentialState(ClearCredentialStateRequest request,
                 IClearCredentialStateCallback callback) {
             Objects.requireNonNull(request);
             Objects.requireNonNull(callback);
 
             ICancellationSignal transport = CancellationSignal.createTransport();
+            try {
+                callback.onCancellable(transport);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
 
             mHandler.sendMessage(obtainMessage(
                     CredentialProviderService::onClearCredentialState,
@@ -350,7 +364,6 @@
                         }
                     }
             ));
-            return transport;
         }
     };
 
diff --git a/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl b/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl
index ab855ef..4b73cbc 100644
--- a/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl
+++ b/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl
@@ -1,6 +1,7 @@
 package android.service.credentials;
 
 import android.service.credentials.BeginCreateCredentialResponse;
+import android.os.ICancellationSignal;
 
 /**
  * Interface from the system to a credential provider service.
@@ -10,4 +11,5 @@
 oneway interface IBeginCreateCredentialCallback {
     void onSuccess(in BeginCreateCredentialResponse request);
     void onFailure(String errorType, in CharSequence message);
+    void onCancellable(in ICancellationSignal cancellation);
 }
\ No newline at end of file
diff --git a/core/java/android/service/credentials/IBeginGetCredentialCallback.aidl b/core/java/android/service/credentials/IBeginGetCredentialCallback.aidl
index 73e9870..0710fb3 100644
--- a/core/java/android/service/credentials/IBeginGetCredentialCallback.aidl
+++ b/core/java/android/service/credentials/IBeginGetCredentialCallback.aidl
@@ -1,6 +1,8 @@
 package android.service.credentials;
 
 import android.service.credentials.BeginGetCredentialResponse;
+import android.os.ICancellationSignal;
+
 
 /**
  * Interface from the system to a credential provider service.
@@ -10,4 +12,5 @@
 oneway interface IBeginGetCredentialCallback {
     void onSuccess(in BeginGetCredentialResponse response);
     void onFailure(String errorType, in CharSequence message);
+    void onCancellable(in ICancellationSignal cancellation);
 }
\ No newline at end of file
diff --git a/core/java/android/service/credentials/IClearCredentialStateCallback.aidl b/core/java/android/service/credentials/IClearCredentialStateCallback.aidl
index ec805d0..5751318 100644
--- a/core/java/android/service/credentials/IClearCredentialStateCallback.aidl
+++ b/core/java/android/service/credentials/IClearCredentialStateCallback.aidl
@@ -16,12 +16,16 @@
 
 package android.service.credentials;
 
+import android.os.ICancellationSignal;
+
+
 /**
  * Callback for onClearCredentialState request.
  *
  * @hide
  */
-interface IClearCredentialStateCallback {
-    oneway void onSuccess();
-    oneway void onFailure(String errorType, CharSequence message);
+oneway interface IClearCredentialStateCallback {
+    void onSuccess();
+    void onFailure(String errorType, CharSequence message);
+    void onCancellable(in ICancellationSignal cancellation);
 }
\ No newline at end of file
diff --git a/core/java/android/service/credentials/ICredentialProviderService.aidl b/core/java/android/service/credentials/ICredentialProviderService.aidl
index 626dd78..ebb4a4e 100644
--- a/core/java/android/service/credentials/ICredentialProviderService.aidl
+++ b/core/java/android/service/credentials/ICredentialProviderService.aidl
@@ -30,8 +30,8 @@
  *
  * @hide
  */
-interface ICredentialProviderService {
-    ICancellationSignal onBeginGetCredential(in BeginGetCredentialRequest request, in IBeginGetCredentialCallback callback);
-    ICancellationSignal onBeginCreateCredential(in BeginCreateCredentialRequest request, in IBeginCreateCredentialCallback callback);
-    ICancellationSignal onClearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback);
+oneway interface ICredentialProviderService {
+    void onBeginGetCredential(in BeginGetCredentialRequest request, in IBeginGetCredentialCallback callback);
+    void onBeginCreateCredential(in BeginCreateCredentialRequest request, in IBeginCreateCredentialCallback callback);
+    void onClearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback);
 }
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 402da28..828c062 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -45,7 +45,6 @@
 import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.PluralsMessageFormatter;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
@@ -59,7 +58,6 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Date;
@@ -310,86 +308,6 @@
         return buffer.toString();
     }
 
-    public Diff diff(ZenModeConfig to) {
-        final Diff d = new Diff();
-        if (to == null) {
-            return d.addLine("config", "delete");
-        }
-        if (user != to.user) {
-            d.addLine("user", user, to.user);
-        }
-        if (allowAlarms != to.allowAlarms) {
-            d.addLine("allowAlarms", allowAlarms, to.allowAlarms);
-        }
-        if (allowMedia != to.allowMedia) {
-            d.addLine("allowMedia", allowMedia, to.allowMedia);
-        }
-        if (allowSystem != to.allowSystem) {
-            d.addLine("allowSystem", allowSystem, to.allowSystem);
-        }
-        if (allowCalls != to.allowCalls) {
-            d.addLine("allowCalls", allowCalls, to.allowCalls);
-        }
-        if (allowReminders != to.allowReminders) {
-            d.addLine("allowReminders", allowReminders, to.allowReminders);
-        }
-        if (allowEvents != to.allowEvents) {
-            d.addLine("allowEvents", allowEvents, to.allowEvents);
-        }
-        if (allowRepeatCallers != to.allowRepeatCallers) {
-            d.addLine("allowRepeatCallers", allowRepeatCallers, to.allowRepeatCallers);
-        }
-        if (allowMessages != to.allowMessages) {
-            d.addLine("allowMessages", allowMessages, to.allowMessages);
-        }
-        if (allowCallsFrom != to.allowCallsFrom) {
-            d.addLine("allowCallsFrom", allowCallsFrom, to.allowCallsFrom);
-        }
-        if (allowMessagesFrom != to.allowMessagesFrom) {
-            d.addLine("allowMessagesFrom", allowMessagesFrom, to.allowMessagesFrom);
-        }
-        if (suppressedVisualEffects != to.suppressedVisualEffects) {
-            d.addLine("suppressedVisualEffects", suppressedVisualEffects,
-                    to.suppressedVisualEffects);
-        }
-        final ArraySet<String> allRules = new ArraySet<>();
-        addKeys(allRules, automaticRules);
-        addKeys(allRules, to.automaticRules);
-        final int N = allRules.size();
-        for (int i = 0; i < N; i++) {
-            final String rule = allRules.valueAt(i);
-            final ZenRule fromRule = automaticRules != null ? automaticRules.get(rule) : null;
-            final ZenRule toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
-            ZenRule.appendDiff(d, "automaticRule[" + rule + "]", fromRule, toRule);
-        }
-        ZenRule.appendDiff(d, "manualRule", manualRule, to.manualRule);
-
-        if (areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
-            d.addLine("areChannelsBypassingDnd", areChannelsBypassingDnd,
-                    to.areChannelsBypassingDnd);
-        }
-        return d;
-    }
-
-    public static Diff diff(ZenModeConfig from, ZenModeConfig to) {
-        if (from == null) {
-            final Diff d = new Diff();
-            if (to != null) {
-                d.addLine("config", "insert");
-            }
-            return d;
-        }
-        return from.diff(to);
-    }
-
-    private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
-        if (map != null) {
-            for (int i = 0; i < map.size(); i++) {
-                set.add(map.keyAt(i));
-            }
-        }
-    }
-
     public boolean isValid() {
         if (!isValidManualRule(manualRule)) return false;
         final int N = automaticRules.size();
@@ -1922,66 +1840,6 @@
             proto.end(token);
         }
 
-        private static void appendDiff(Diff d, String item, ZenRule from, ZenRule to) {
-            if (d == null) return;
-            if (from == null) {
-                if (to != null) {
-                    d.addLine(item, "insert");
-                }
-                return;
-            }
-            from.appendDiff(d, item, to);
-        }
-
-        private void appendDiff(Diff d, String item, ZenRule to) {
-            if (to == null) {
-                d.addLine(item, "delete");
-                return;
-            }
-            if (enabled != to.enabled) {
-                d.addLine(item, "enabled", enabled, to.enabled);
-            }
-            if (snoozing != to.snoozing) {
-                d.addLine(item, "snoozing", snoozing, to.snoozing);
-            }
-            if (!Objects.equals(name, to.name)) {
-                d.addLine(item, "name", name, to.name);
-            }
-            if (zenMode != to.zenMode) {
-                d.addLine(item, "zenMode", zenMode, to.zenMode);
-            }
-            if (!Objects.equals(conditionId, to.conditionId)) {
-                d.addLine(item, "conditionId", conditionId, to.conditionId);
-            }
-            if (!Objects.equals(condition, to.condition)) {
-                d.addLine(item, "condition", condition, to.condition);
-            }
-            if (!Objects.equals(component, to.component)) {
-                d.addLine(item, "component", component, to.component);
-            }
-            if (!Objects.equals(configurationActivity, to.configurationActivity)) {
-                d.addLine(item, "configActivity", configurationActivity, to.configurationActivity);
-            }
-            if (!Objects.equals(id, to.id)) {
-                d.addLine(item, "id", id, to.id);
-            }
-            if (creationTime != to.creationTime) {
-                d.addLine(item, "creationTime", creationTime, to.creationTime);
-            }
-            if (!Objects.equals(enabler, to.enabler)) {
-                d.addLine(item, "enabler", enabler, to.enabler);
-            }
-            if (!Objects.equals(zenPolicy, to.zenPolicy)) {
-                d.addLine(item, "zenPolicy", zenPolicy, to.zenPolicy);
-            }
-            if (modified != to.modified) {
-                d.addLine(item, "modified", modified, to.modified);
-            }
-            if (!Objects.equals(pkg, to.pkg)) {
-                d.addLine(item, "pkg", pkg, to.pkg);
-            }
-        }
-
         @Override
         public boolean equals(@Nullable Object o) {
             if (!(o instanceof ZenRule)) return false;
@@ -2040,40 +1898,6 @@
         };
     }
 
-    public static class Diff {
-        private final ArrayList<String> lines = new ArrayList<>();
-
-        @Override
-        public String toString() {
-            final StringBuilder sb = new StringBuilder("Diff[");
-            final int N = lines.size();
-            for (int i = 0; i < N; i++) {
-                if (i > 0) {
-                    sb.append(",\n");
-                }
-                sb.append(lines.get(i));
-            }
-            return sb.append(']').toString();
-        }
-
-        private Diff addLine(String item, String action) {
-            lines.add(item + ":" + action);
-            return this;
-        }
-
-        public Diff addLine(String item, String subitem, Object from, Object to) {
-            return addLine(item + "." + subitem, from, to);
-        }
-
-        public Diff addLine(String item, Object from, Object to) {
-            return addLine(item, from + "->" + to);
-        }
-
-        public boolean isEmpty() {
-            return lines.isEmpty();
-        }
-    }
-
     /**
      * Determines whether dnd behavior should mute all ringer-controlled sounds
      * This includes notification, ringer and system sounds
diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java
new file mode 100644
index 0000000..c7b89eb
--- /dev/null
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -0,0 +1,542 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.service.notification;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their
+ * subcomponents (automatic and manual ZenRules).
+ * @hide
+ */
+public class ZenModeDiff {
+    /**
+     * Enum representing whether the existence of a config or rule has changed (added or removed,
+     * or "none" meaning there is no change, which may either mean both null, or there exists a
+     * diff in fields rather than add/remove).
+     */
+    @IntDef(value = {
+            NONE,
+            ADDED,
+            REMOVED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ExistenceChange{}
+
+    public static final int NONE = 0;
+    public static final int ADDED = 1;
+    public static final int REMOVED = 2;
+
+    /**
+     * Diff class representing an individual field diff.
+     * @param <T> The type of the field.
+     */
+    public static class FieldDiff<T> {
+        private final T mFrom;
+        private final T mTo;
+
+        /**
+         * Constructor to create a FieldDiff object with the given values.
+         * @param from from (old) value
+         * @param to to (new) value
+         */
+        public FieldDiff(@Nullable T from, @Nullable T to) {
+            mFrom = from;
+            mTo = to;
+        }
+
+        /**
+         * Get the "from" value
+         */
+        public T from() {
+            return mFrom;
+        }
+
+        /**
+         * Get the "to" value
+         */
+        public T to() {
+            return mTo;
+        }
+
+        /**
+         * Get the string representation of this field diff, in the form of "from->to".
+         */
+        @Override
+        public String toString() {
+            return mFrom + "->" + mTo;
+        }
+
+        /**
+         * Returns whether this represents an actual diff.
+         */
+        public boolean hasDiff() {
+            // note that Objects.equals handles null values gracefully.
+            return !Objects.equals(mFrom, mTo);
+        }
+    }
+
+    /**
+     * Base diff class that contains info about whether something was added, and a set of named
+     * fields that changed.
+     * Extend for diffs of specific types of objects.
+     */
+    private abstract static class BaseDiff {
+        // Whether the diff was added or removed
+        @ExistenceChange private int mExists = NONE;
+
+        // Map from field name to diffs for any standalone fields in the object.
+        private ArrayMap<String, FieldDiff> mFields = new ArrayMap<>();
+
+        // Functions for actually diffing objects and string representations have to be implemented
+        // by subclasses.
+
+        /**
+         * Return whether this diff represents any changes.
+         */
+        public abstract boolean hasDiff();
+
+        /**
+         * Return a string representation of the diff.
+         */
+        public abstract String toString();
+
+        /**
+         * Constructor that takes the two objects meant to be compared. This constructor sets
+         * whether there is an existence change (added or removed).
+         * @param from previous Object
+         * @param to new Object
+         */
+        BaseDiff(Object from, Object to) {
+            if (from == null) {
+                if (to != null) {
+                    mExists = ADDED;
+                }
+                // If both are null, there isn't an existence change; callers/inheritors must handle
+                // the both null case.
+            } else if (to == null) {
+                // in this case, we know that from != null
+                mExists = REMOVED;
+            }
+
+            // Subclasses should implement the actual diffing functionality in their own
+            // constructors.
+        }
+
+        /**
+         * Add a diff for a specific field to the map.
+         * @param name field name
+         * @param diff FieldDiff object representing the diff
+         */
+        final void addField(String name, FieldDiff diff) {
+            mFields.put(name, diff);
+        }
+
+        /**
+         * Returns whether this diff represents a config being newly added.
+         */
+        public final boolean wasAdded() {
+            return mExists == ADDED;
+        }
+
+        /**
+         * Returns whether this diff represents a config being removed.
+         */
+        public final boolean wasRemoved() {
+            return mExists == REMOVED;
+        }
+
+        /**
+         * Returns whether this diff represents an object being either added or removed.
+         */
+        public final boolean hasExistenceChange() {
+            return mExists != NONE;
+        }
+
+        /**
+         * Returns whether there are any individual field diffs.
+         */
+        public final boolean hasFieldDiffs() {
+            return mFields.size() > 0;
+        }
+
+        /**
+         * Returns the diff for the specific named field if it exists
+         */
+        public final FieldDiff getDiffForField(String name) {
+            return mFields.getOrDefault(name, null);
+        }
+
+        /**
+         * Get the set of all field names with some diff.
+         */
+        public final Set<String> fieldNamesWithDiff() {
+            return mFields.keySet();
+        }
+    }
+
+    /**
+     * Diff class representing a diff between two ZenModeConfigs.
+     */
+    public static class ConfigDiff extends BaseDiff {
+        // Rules. Automatic rule map is keyed by the rule name.
+        private final ArrayMap<String, RuleDiff> mAutomaticRulesDiff = new ArrayMap<>();
+        private RuleDiff mManualRuleDiff;
+
+        // Helpers for string generation
+        private static final String ALLOW_CALLS_FROM_FIELD = "allowCallsFrom";
+        private static final String ALLOW_MESSAGES_FROM_FIELD = "allowMessagesFrom";
+        private static final String ALLOW_CONVERSATIONS_FROM_FIELD = "allowConversationsFrom";
+        private static final Set<String> PEOPLE_TYPE_FIELDS =
+                Set.of(ALLOW_CALLS_FROM_FIELD, ALLOW_MESSAGES_FROM_FIELD);
+
+        /**
+         * Create a diff that contains diffs between the "from" and "to" ZenModeConfigs.
+         *
+         * @param from previous ZenModeConfig
+         * @param to   new ZenModeConfig
+         */
+        public ConfigDiff(ZenModeConfig from, ZenModeConfig to) {
+            super(from, to);
+            // If both are null skip
+            if (from == null && to == null) {
+                return;
+            }
+            if (hasExistenceChange()) {
+                // either added or removed; return here. otherwise (they're not both null) there's
+                // field diffs.
+                return;
+            }
+
+            // Now we compare all the fields, knowing there's a diff and that neither is null
+            if (from.user != to.user) {
+                addField("user", new FieldDiff<>(from.user, to.user));
+            }
+            if (from.allowAlarms != to.allowAlarms) {
+                addField("allowAlarms", new FieldDiff<>(from.allowAlarms, to.allowAlarms));
+            }
+            if (from.allowMedia != to.allowMedia) {
+                addField("allowMedia", new FieldDiff<>(from.allowMedia, to.allowMedia));
+            }
+            if (from.allowSystem != to.allowSystem) {
+                addField("allowSystem", new FieldDiff<>(from.allowSystem, to.allowSystem));
+            }
+            if (from.allowCalls != to.allowCalls) {
+                addField("allowCalls", new FieldDiff<>(from.allowCalls, to.allowCalls));
+            }
+            if (from.allowReminders != to.allowReminders) {
+                addField("allowReminders",
+                        new FieldDiff<>(from.allowReminders, to.allowReminders));
+            }
+            if (from.allowEvents != to.allowEvents) {
+                addField("allowEvents", new FieldDiff<>(from.allowEvents, to.allowEvents));
+            }
+            if (from.allowRepeatCallers != to.allowRepeatCallers) {
+                addField("allowRepeatCallers",
+                        new FieldDiff<>(from.allowRepeatCallers, to.allowRepeatCallers));
+            }
+            if (from.allowMessages != to.allowMessages) {
+                addField("allowMessages",
+                        new FieldDiff<>(from.allowMessages, to.allowMessages));
+            }
+            if (from.allowConversations != to.allowConversations) {
+                addField("allowConversations",
+                        new FieldDiff<>(from.allowConversations, to.allowConversations));
+            }
+            if (from.allowCallsFrom != to.allowCallsFrom) {
+                addField("allowCallsFrom",
+                        new FieldDiff<>(from.allowCallsFrom, to.allowCallsFrom));
+            }
+            if (from.allowMessagesFrom != to.allowMessagesFrom) {
+                addField("allowMessagesFrom",
+                        new FieldDiff<>(from.allowMessagesFrom, to.allowMessagesFrom));
+            }
+            if (from.allowConversationsFrom != to.allowConversationsFrom) {
+                addField("allowConversationsFrom",
+                        new FieldDiff<>(from.allowConversationsFrom, to.allowConversationsFrom));
+            }
+            if (from.suppressedVisualEffects != to.suppressedVisualEffects) {
+                addField("suppressedVisualEffects",
+                        new FieldDiff<>(from.suppressedVisualEffects, to.suppressedVisualEffects));
+            }
+            if (from.areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
+                addField("areChannelsBypassingDnd",
+                        new FieldDiff<>(from.areChannelsBypassingDnd, to.areChannelsBypassingDnd));
+            }
+
+            // Compare automatic and manual rules
+            final ArraySet<String> allRules = new ArraySet<>();
+            addKeys(allRules, from.automaticRules);
+            addKeys(allRules, to.automaticRules);
+            final int num = allRules.size();
+            for (int i = 0; i < num; i++) {
+                final String rule = allRules.valueAt(i);
+                final ZenModeConfig.ZenRule
+                        fromRule = from.automaticRules != null ? from.automaticRules.get(rule)
+                        : null;
+                final ZenModeConfig.ZenRule
+                        toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
+                RuleDiff ruleDiff = new RuleDiff(fromRule, toRule);
+                if (ruleDiff.hasDiff()) {
+                    mAutomaticRulesDiff.put(rule, ruleDiff);
+                }
+            }
+            // If there's no diff this may turn out to be null, but that's also fine
+            RuleDiff manualRuleDiff = new RuleDiff(from.manualRule, to.manualRule);
+            if (manualRuleDiff.hasDiff()) {
+                mManualRuleDiff = manualRuleDiff;
+            }
+        }
+
+        private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
+            if (map != null) {
+                for (int i = 0; i < map.size(); i++) {
+                    set.add(map.keyAt(i));
+                }
+            }
+        }
+
+        /**
+         * Returns whether this diff object contains any diffs in any field.
+         */
+        @Override
+        public boolean hasDiff() {
+            return hasExistenceChange()
+                    || hasFieldDiffs()
+                    || mManualRuleDiff != null
+                    || mAutomaticRulesDiff.size() > 0;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder("Diff[");
+            if (!hasDiff()) {
+                sb.append("no changes");
+            }
+
+            // If added or deleted, then that's just the end of it
+            if (hasExistenceChange()) {
+                if (wasAdded()) {
+                    sb.append("added");
+                } else if (wasRemoved()) {
+                    sb.append("removed");
+                }
+            }
+
+            // Handle top-level field change
+            boolean first = true;
+            for (String key : fieldNamesWithDiff()) {
+                FieldDiff diff = getDiffForField(key);
+                if (diff == null) {
+                    // this shouldn't happen, but
+                    continue;
+                }
+                if (first) {
+                    first = false;
+                } else {
+                    sb.append(",\n");
+                }
+
+                // Some special handling for people- and conversation-type fields for readability
+                if (PEOPLE_TYPE_FIELDS.contains(key)) {
+                    sb.append(key);
+                    sb.append(":");
+                    sb.append(ZenModeConfig.sourceToString((int) diff.from()));
+                    sb.append("->");
+                    sb.append(ZenModeConfig.sourceToString((int) diff.to()));
+                } else if (key.equals(ALLOW_CONVERSATIONS_FROM_FIELD)) {
+                    sb.append(key);
+                    sb.append(":");
+                    sb.append(ZenPolicy.conversationTypeToString((int) diff.from()));
+                    sb.append("->");
+                    sb.append(ZenPolicy.conversationTypeToString((int) diff.to()));
+                } else {
+                    sb.append(key);
+                    sb.append(":");
+                    sb.append(diff);
+                }
+            }
+
+            // manual rule
+            if (mManualRuleDiff != null && mManualRuleDiff.hasDiff()) {
+                if (first) {
+                    first = false;
+                } else {
+                    sb.append(",\n");
+                }
+                sb.append("manualRule:");
+                sb.append(mManualRuleDiff);
+            }
+
+            // automatic rules
+            for (String rule : mAutomaticRulesDiff.keySet()) {
+                RuleDiff diff = mAutomaticRulesDiff.get(rule);
+                if (diff != null && diff.hasDiff()) {
+                    if (first) {
+                        first = false;
+                    } else {
+                        sb.append(",\n");
+                    }
+                    sb.append("automaticRule[");
+                    sb.append(rule);
+                    sb.append("]:");
+                    sb.append(diff);
+                }
+            }
+
+            return sb.append(']').toString();
+        }
+
+        /**
+         * Get the diff in manual rule, if it exists.
+         */
+        public RuleDiff getManualRuleDiff() {
+            return mManualRuleDiff;
+        }
+
+        /**
+         * Get the full map of automatic rule diffs, or null if there are no diffs.
+         */
+        public ArrayMap<String, RuleDiff> getAllAutomaticRuleDiffs() {
+            return (mAutomaticRulesDiff.size() > 0) ? mAutomaticRulesDiff : null;
+        }
+    }
+
+    /**
+     * Diff class representing a change between two ZenRules.
+     */
+    public static class RuleDiff extends BaseDiff {
+        /**
+         * Create a RuleDiff representing the difference between two ZenRule objects.
+         * @param from previous ZenRule
+         * @param to new ZenRule
+         * @return The diff between the two given ZenRules
+         */
+        public RuleDiff(ZenModeConfig.ZenRule from, ZenModeConfig.ZenRule to) {
+            super(from, to);
+            // Short-circuit the both-null case
+            if (from == null && to == null) {
+                return;
+            }
+            // Return if the diff was added or removed
+            if (hasExistenceChange()) {
+                return;
+            }
+
+            if (from.enabled != to.enabled) {
+                addField("enabled", new FieldDiff<>(from.enabled, to.enabled));
+            }
+            if (from.snoozing != to.snoozing) {
+                addField("snoozing", new FieldDiff<>(from.snoozing, to.snoozing));
+            }
+            if (!Objects.equals(from.name, to.name)) {
+                addField("name", new FieldDiff<>(from.name, to.name));
+            }
+            if (from.zenMode != to.zenMode) {
+                addField("zenMode", new FieldDiff<>(from.zenMode, to.zenMode));
+            }
+            if (!Objects.equals(from.conditionId, to.conditionId)) {
+                addField("conditionId", new FieldDiff<>(from.conditionId, to.conditionId));
+            }
+            if (!Objects.equals(from.condition, to.condition)) {
+                addField("condition", new FieldDiff<>(from.condition, to.condition));
+            }
+            if (!Objects.equals(from.component, to.component)) {
+                addField("component", new FieldDiff<>(from.component, to.component));
+            }
+            if (!Objects.equals(from.configurationActivity, to.configurationActivity)) {
+                addField("configurationActivity", new FieldDiff<>(
+                        from.configurationActivity, to.configurationActivity));
+            }
+            if (!Objects.equals(from.id, to.id)) {
+                addField("id", new FieldDiff<>(from.id, to.id));
+            }
+            if (from.creationTime != to.creationTime) {
+                addField("creationTime",
+                        new FieldDiff<>(from.creationTime, to.creationTime));
+            }
+            if (!Objects.equals(from.enabler, to.enabler)) {
+                addField("enabler", new FieldDiff<>(from.enabler, to.enabler));
+            }
+            if (!Objects.equals(from.zenPolicy, to.zenPolicy)) {
+                addField("zenPolicy", new FieldDiff<>(from.zenPolicy, to.zenPolicy));
+            }
+            if (from.modified != to.modified) {
+                addField("modified", new FieldDiff<>(from.modified, to.modified));
+            }
+            if (!Objects.equals(from.pkg, to.pkg)) {
+                addField("pkg", new FieldDiff<>(from.pkg, to.pkg));
+            }
+        }
+
+        /**
+         * Returns whether this object represents an actual diff.
+         */
+        @Override
+        public boolean hasDiff() {
+            return hasExistenceChange() || hasFieldDiffs();
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder("ZenRuleDiff{");
+            // If there's no diff, probably we haven't actually let this object continue existing
+            // but might as well handle this case.
+            if (!hasDiff()) {
+                sb.append("no changes");
+            }
+
+            // If added or deleted, then that's just the end of it
+            if (hasExistenceChange()) {
+                if (wasAdded()) {
+                    sb.append("added");
+                } else if (wasRemoved()) {
+                    sb.append("removed");
+                }
+            }
+
+            // Go through all of the individual fields
+            boolean first = true;
+            for (String key : fieldNamesWithDiff()) {
+                FieldDiff diff = getDiffForField(key);
+                if (diff == null) {
+                    // this shouldn't happen, but
+                    continue;
+                }
+                if (first) {
+                    first = false;
+                } else {
+                    sb.append(", ");
+                }
+
+                sb.append(key);
+                sb.append(":");
+                sb.append(diff);
+            }
+
+            return sb.append("}").toString();
+        }
+    }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index fcc64b0..68cce4a 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -50,6 +50,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceActionCheckCallback;
 import com.android.internal.app.IVoiceInteractionManagerService;
@@ -200,6 +201,9 @@
 
     private final Set<HotwordDetector> mActiveDetectors = new ArraySet<>();
 
+    // True if any of the createAOHD methods should use the test ST module.
+    @GuardedBy("mLock")
+    private boolean mTestModuleForAlwaysOnHotwordDetectorEnabled = false;
 
     private void onDetectorRemoteException(@NonNull IBinder token, int detectorType) {
         Log.d(TAG, "onDetectorRemoteException for " + HotwordDetector.detectorTypeToString(
@@ -512,14 +516,14 @@
         Objects.requireNonNull(callback);
         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
                 /* supportHotwordDetectionService= */ false, /* options= */ null,
-                /* sharedMemory= */ null, /* moduleProperties */ null, executor, callback);
+                /* sharedMemory= */ null, /* moduleProperties= */ null, executor, callback);
     }
 
     /**
      * Same as {@link createAlwaysOnHotwordDetector(String, Locale, Executor,
      * AlwaysOnHotwordDetector.Callback)}, but allow explicit selection of the underlying ST
      * module to attach to.
-     * Use {@link listModuleProperties} to get available modules to attach to.
+     * Use {@link #listModuleProperties()} to get available modules to attach to.
      * @hide
      */
     @TestApi
@@ -645,14 +649,14 @@
         Objects.requireNonNull(callback);
         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
                 /* supportHotwordDetectionService= */ true, options, sharedMemory,
-                /* moduleProperties */ null, executor, callback);
+                /* moduleProperties= */ null, executor, callback);
     }
 
     /**
      * Same as {@link createAlwaysOnHotwordDetector(String, Locale,
      * PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)},
      * but allow explicit selection of the underlying ST module to attach to.
-     * Use {@link listModuleProperties} to get available modules to attach to.
+     * Use {@link #listModuleProperties()} to get available modules to attach to.
      * @hide
      */
     @TestApi
@@ -717,6 +721,10 @@
 
             try {
                 dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
+                // Check if we are currently overridden, and should use the test module.
+                if (mTestModuleForAlwaysOnHotwordDetectorEnabled) {
+                    moduleProperties = getTestModuleProperties();
+                }
                 // If moduleProperties is null, the default STModule is used.
                 dspDetector.initialize(options, sharedMemory, moduleProperties);
             } catch (Exception e) {
@@ -990,6 +998,44 @@
         return mKeyphraseEnrollmentInfo;
     }
 
+
+    /**
+     * Configure {@link createAlwaysOnHotwordDetector(String, Locale,
+     * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)}
+     * and similar overloads to utilize the test SoundTrigger module instead of the
+     * actual DSP module.
+     * @param isEnabled - {@code true} if subsequently created {@link AlwaysOnHotwordDetector}
+     * objects should attach to a test module. {@code false} if subsequently created
+     * {@link AlwaysOnHotwordDetector} should attach to the actual DSP module.
+     * @hide
+     */
+    @TestApi
+    public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean isEnabled) {
+        synchronized (mLock) {
+            mTestModuleForAlwaysOnHotwordDetectorEnabled = isEnabled;
+        }
+    }
+
+    /**
+     * Get the {@link SoundTrigger.ModuleProperties} representing the fake
+     * STHAL to attach to via {@link createAlwaysOnHotwordDetector(String, Locale,
+     * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} and
+     * similar overloads for test purposes.
+     * @return ModuleProperties to use for test purposes.
+     */
+    private final @NonNull SoundTrigger.ModuleProperties getTestModuleProperties() {
+        var moduleProps = listModuleProperties()
+                .stream()
+                .filter((SoundTrigger.ModuleProperties prop)
+                        -> prop.getSupportedModelArch().equals(SoundTrigger.FAKE_HAL_ARCH))
+                .findFirst()
+                .orElse(null);
+        if (moduleProps == null) {
+            throw new IllegalStateException("Fake ST HAL should always be available");
+        }
+        return moduleProps;
+    }
+
     /**
      * Checks if a given keyphrase and locale are supported to create an
      * {@link AlwaysOnHotwordDetector}.
diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java
index dae978e..9f4a0ae 100644
--- a/core/java/android/text/method/LinkMovementMethod.java
+++ b/core/java/android/text/method/LinkMovementMethod.java
@@ -221,12 +221,20 @@
             y += widget.getScrollY();
 
             Layout layout = widget.getLayout();
-            int line = layout.getLineForVertical(y);
-            int off = layout.getOffsetForHorizontal(line, x);
+            ClickableSpan[] links;
+            if (y < 0 || y > layout.getHeight()) {
+                links = null;
+            } else {
+                int line = layout.getLineForVertical(y);
+                if (x < layout.getLineLeft(line) || x > layout.getLineRight(line)) {
+                    links = null;
+                } else {
+                    int off = layout.getOffsetForHorizontal(line, x);
+                    links = buffer.getSpans(off, off, ClickableSpan.class);
+                }
+            }
 
-            ClickableSpan[] links = buffer.getSpans(off, off, ClickableSpan.class);
-
-            if (links.length != 0) {
+            if (links != null && links.length != 0) {
                 ClickableSpan link = links[0];
                 if (action == MotionEvent.ACTION_UP) {
                     if (link instanceof TextLinkSpan) {
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index c92b1b8..8c4e90c 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -195,7 +195,7 @@
 
     private boolean mDebugPrintNextFrameTimeDelta;
     private int mFPSDivisor = 1;
-    private final DisplayEventReceiver.VsyncEventData mLastVsyncEventData =
+    private DisplayEventReceiver.VsyncEventData mLastVsyncEventData =
             new DisplayEventReceiver.VsyncEventData();
     private final FrameData mFrameData = new FrameData();
 
@@ -857,7 +857,7 @@
                 mFrameScheduled = false;
                 mLastFrameTimeNanos = frameTimeNanos;
                 mLastFrameIntervalNanos = frameIntervalNanos;
-                mLastVsyncEventData.copyFrom(vsyncEventData);
+                mLastVsyncEventData = vsyncEventData;
             }
 
             AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
@@ -1247,7 +1247,7 @@
         private boolean mHavePendingVsync;
         private long mTimestampNanos;
         private int mFrame;
-        private final VsyncEventData mLastVsyncEventData = new VsyncEventData();
+        private VsyncEventData mLastVsyncEventData = new VsyncEventData();
 
         FrameDisplayEventReceiver(Looper looper, int vsyncSource, long layerHandle) {
             super(looper, vsyncSource, /* eventRegistration */ 0, layerHandle);
@@ -1287,7 +1287,7 @@
 
                 mTimestampNanos = timestampNanos;
                 mFrame = frame;
-                mLastVsyncEventData.copyFrom(vsyncEventData);
+                mLastVsyncEventData = vsyncEventData;
                 Message msg = Message.obtain(mHandler, this);
                 msg.setAsynchronous(true);
                 mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 54db34e..0307489 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -81,10 +81,7 @@
     // GC'd while the native peer of the receiver is using them.
     private MessageQueue mMessageQueue;
 
-    private final VsyncEventData mVsyncEventData = new VsyncEventData();
-
     private static native long nativeInit(WeakReference<DisplayEventReceiver> receiver,
-            WeakReference<VsyncEventData> vsyncEventData,
             MessageQueue messageQueue, int vsyncSource, int eventRegistration, long layerHandle);
     private static native long nativeGetDisplayEventReceiverFinalizer();
     @FastNative
@@ -127,9 +124,7 @@
         }
 
         mMessageQueue = looper.getQueue();
-        mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this),
-                new WeakReference<VsyncEventData>(mVsyncEventData),
-                mMessageQueue,
+        mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
                 vsyncSource, eventRegistration, layerHandle);
         mFreeNativeResources = sNativeAllocationRegistry.registerNativeAllocation(this,
                 mReceiverPtr);
@@ -152,6 +147,9 @@
      * @hide
      */
     public static final class VsyncEventData {
+        static final FrameTimeline[] INVALID_FRAME_TIMELINES =
+                {new FrameTimeline(FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE, Long.MAX_VALUE)};
+
         // The amount of frame timeline choices.
         // Must be in sync with VsyncEventData::kFrameTimelinesLength in
         // frameworks/native/libs/gui/include/gui/VsyncEventData.h. If they do not match, a runtime
@@ -159,32 +157,22 @@
         static final int FRAME_TIMELINES_LENGTH = 7;
 
         public static class FrameTimeline {
-            FrameTimeline() {}
-
-            // Called from native code.
-            @SuppressWarnings("unused")
             FrameTimeline(long vsyncId, long expectedPresentationTime, long deadline) {
                 this.vsyncId = vsyncId;
                 this.expectedPresentationTime = expectedPresentationTime;
                 this.deadline = deadline;
             }
 
-            void copyFrom(FrameTimeline other) {
-                vsyncId = other.vsyncId;
-                expectedPresentationTime = other.expectedPresentationTime;
-                deadline = other.deadline;
-            }
-
             // The frame timeline vsync id, used to correlate a frame
             // produced by HWUI with the timeline data stored in Surface Flinger.
-            public long vsyncId = FrameInfo.INVALID_VSYNC_ID;
+            public final long vsyncId;
 
             // The frame timestamp for when the frame is expected to be presented.
-            public long expectedPresentationTime = Long.MAX_VALUE;
+            public final long expectedPresentationTime;
 
             // The frame deadline timestamp in {@link System#nanoTime()} timebase that it is
             // allotted for the frame to be completed.
-            public long deadline = Long.MAX_VALUE;
+            public final long deadline;
         }
 
         /**
@@ -192,18 +180,11 @@
          * {@link FrameInfo#VSYNC} to the current vsync in case Choreographer callback was heavily
          * delayed by the app.
          */
-        public long frameInterval = -1;
+        public final long frameInterval;
 
         public final FrameTimeline[] frameTimelines;
 
-        public int preferredFrameTimelineIndex = 0;
-
-        VsyncEventData() {
-            frameTimelines = new FrameTimeline[FRAME_TIMELINES_LENGTH];
-            for (int i = 0; i < frameTimelines.length; i++) {
-                frameTimelines[i] = new FrameTimeline();
-            }
-        }
+        public final int preferredFrameTimelineIndex;
 
         // Called from native code.
         @SuppressWarnings("unused")
@@ -214,12 +195,10 @@
             this.frameInterval = frameInterval;
         }
 
-        void copyFrom(VsyncEventData other) {
-            preferredFrameTimelineIndex = other.preferredFrameTimelineIndex;
-            frameInterval = other.frameInterval;
-            for (int i = 0; i < frameTimelines.length; i++) {
-                frameTimelines[i].copyFrom(other.frameTimelines[i]);
-            }
+        VsyncEventData() {
+            this.frameInterval = -1;
+            this.frameTimelines = INVALID_FRAME_TIMELINES;
+            this.preferredFrameTimelineIndex = 0;
         }
 
         public FrameTimeline preferredFrameTimeline() {
@@ -325,8 +304,9 @@
 
     // Called from native code.
     @SuppressWarnings("unused")
-    private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
-        onVsync(timestampNanos, physicalDisplayId, frame, mVsyncEventData);
+    private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,
+            VsyncEventData vsyncEventData) {
+        onVsync(timestampNanos, physicalDisplayId, frame, vsyncEventData);
     }
 
     // Called from native code.
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index d35aff9..3812d37 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -215,6 +215,7 @@
                 .append(", scaleFactor=").append(scaleFactor)
                 .append(", transform=").append(transform)
                 .append(", windowToken=").append(windowToken)
+                .append(", displayId=").append(displayId)
                 .append(", isClone=").append((inputConfig & InputConfig.CLONE) != 0)
                 .toString();
 
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index d88994b..fee88d91 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -146,7 +146,14 @@
     // conflicts with any system types that may be defined in the future.
     private static final int TYPE_OEM_FIRST = 10000;
 
-    /** The default pointer icon. */
+    /**
+     * The default pointer icon.
+     * @deprecated This is the same as using {@link #TYPE_ARROW}. Use {@link #TYPE_ARROW} to
+     *     explicitly show an arrow, or use a {@code null} {@link PointerIcon} with
+     *     {@link View#setPointerIcon(PointerIcon)} or
+     *     {@link View#onResolvePointerIcon(MotionEvent, int)} instead to show
+     *     the default pointer icon.
+     */
     public static final int TYPE_DEFAULT = TYPE_ARROW;
 
     private static final PointerIcon gNullIcon = new PointerIcon(TYPE_NULL);
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index ac50d09..cd89a56 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -99,9 +99,16 @@
         @Override
         public ISurfaceSyncGroup getSurfaceSyncGroup() {
             CompletableFuture<ISurfaceSyncGroup> surfaceSyncGroup = new CompletableFuture<>();
-            mViewRoot.mHandler.post(
-                    () -> surfaceSyncGroup.complete(
-                            mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup));
+            // If the call came from in process and it's already running on the UI thread, return
+            // results immediately instead of posting to the main thread. If we post to the main
+            // thread, it will block itself and the return value will always be null.
+            if (Thread.currentThread() == mViewRoot.mThread) {
+                return mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup;
+            } else {
+                mViewRoot.mHandler.post(
+                        () -> surfaceSyncGroup.complete(
+                                mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup));
+            }
             try {
                 return surfaceSyncGroup.get(1, TimeUnit.SECONDS);
             } catch (InterruptedException | ExecutionException | TimeoutException e) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3a281b3..6eaf6ba 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6946,6 +6946,7 @@
                 return;
             }
             final boolean needsStylusPointerIcon = event.isStylusPointer()
+                    && event.isHoverEvent()
                     && mInputManager.isStylusPointerIconEnabled();
             if (needsStylusPointerIcon || event.isFromSource(InputDevice.SOURCE_MOUSE)) {
                 if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index 11fd1c5..7f8926d 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -975,6 +975,7 @@
         append(builder, "AddedCount", mAddedCount);
         append(builder, "RemovedCount", mRemovedCount);
         append(builder, "ParcelableData", mParcelableData);
+        append(builder, "DisplayId", mSourceDisplayId);
         builder.append(" ]");
         return builder;
     }
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 3df09c2..1f0e95e 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -49,15 +49,18 @@
 import java.util.Locale;
 
 /**
- * This widget display an analogic clock with two hands for hours and
- * minutes.
+ * This widget displays an analogic clock with two hands for hours and minutes.
  *
  * @attr ref android.R.styleable#AnalogClock_dial
  * @attr ref android.R.styleable#AnalogClock_hand_hour
  * @attr ref android.R.styleable#AnalogClock_hand_minute
  * @attr ref android.R.styleable#AnalogClock_hand_second
  * @attr ref android.R.styleable#AnalogClock_timeZone
- * @deprecated This widget is no longer supported.
+ * @deprecated This widget is no longer supported; except for
+ * {@link android.widget.RemoteViews} use cases like
+ * <a href="https://developer.android.com/develop/ui/views/appwidgets/overview">
+ * app widgets</a>.
+ *
  */
 @RemoteView
 @Deprecated
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 088065d2..7931d1a 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -8095,12 +8095,14 @@
         private boolean mIsInsertModeActive;
         private InsertModeTransformationMethod mInsertModeTransformationMethod;
         private final Paint mHighlightPaint;
+        private final Path mHighlightPath;
 
         InsertModeController(@NonNull TextView textView) {
             mTextView = Objects.requireNonNull(textView);
             mIsInsertModeActive = false;
             mInsertModeTransformationMethod = null;
             mHighlightPaint = new Paint();
+            mHighlightPath = new Path();
 
             // The highlight color is supposed to be 12% of the color primary40. We can't
             // directly access Material 3 theme. But because Material 3 sets the colorPrimary to
@@ -8168,10 +8170,8 @@
                         ((InsertModeTransformationMethod.TransformedText) transformedText);
                 final int highlightStart = insertModeTransformedText.getHighlightStart();
                 final int highlightEnd = insertModeTransformedText.getHighlightEnd();
-                final Layout.SelectionRectangleConsumer consumer =
-                        (left, top, right, bottom, textSelectionLayout) ->
-                                canvas.drawRect(left, top, right, bottom, mHighlightPaint);
-                layout.getSelection(highlightStart, highlightEnd, consumer);
+                layout.getSelectionPath(highlightStart, highlightEnd, mHighlightPath);
+                canvas.drawPath(mHighlightPath, mHighlightPaint);
             }
         }
 
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 18874f7..3165654 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1780,6 +1780,21 @@
             Object value = getParameterValue(view);
             try {
                 MethodHandle method = getMethod(view, this.methodName, param, true /* async */);
+                // Upload the bitmap to GPU if the parameter is of type Bitmap or Icon.
+                // Since bitmaps in framework are seldomly modified, this is supposed to accelerate
+                // the operations.
+                if (value instanceof Bitmap bitmap) {
+                    bitmap.prepareToDraw();
+                }
+
+                if (value instanceof Icon icon
+                        && (icon.getType() == Icon.TYPE_BITMAP
+                                || icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP)) {
+                    Bitmap bitmap = icon.getBitmap();
+                    if (bitmap != null) {
+                        bitmap.prepareToDraw();
+                    }
+                }
 
                 if (method != null) {
                     Runnable endAction = (Runnable) method.invoke(view, value);
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index ab7f602..ed751cb 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -16,8 +16,9 @@
 
 package com.android.internal.app;
 
-import android.media.permission.Identity;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.media.permission.Identity;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
 import com.android.internal.app.ISoundTriggerSession;
 
 /**
@@ -74,4 +75,8 @@
      */
     List<SoundTrigger.ModuleProperties> listModuleProperties(in Identity originatorIdentity);
 
+    /**
+     * Attach an HAL injection interface.
+     */
+     void attachInjection(ISoundTriggerInjection injection);
 }
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 6b40d98..24d5afc 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -96,6 +96,21 @@
      * @RequiresPermission Manifest.permission.MANAGE_VOICE_KEYPHRASES
      */
     int deleteKeyphraseSoundModel(int keyphraseId, in String bcp47Locale);
+
+    /**
+     * Override the persistent enrolled model database with an in-memory
+     * fake for testing purposes.
+     *
+     * @param enabled - {@code true} to enable the test database. {@code false} to enable
+     * the real, persistent database.
+     * @param token - IBinder used to register a death listener to clean-up the override
+     * if tests do not clean up gracefully.
+     */
+    @EnforcePermission("MANAGE_VOICE_KEYPHRASES")
+    @JavaPassthrough(annotation= "@android.annotation.RequiresPermission(" +
+            "android.Manifest.permission.MANAGE_VOICE_KEYPHRASES)")
+    void setModelDatabaseForTestEnabled(boolean enabled, IBinder token);
+
     /**
      * Indicates if there's a keyphrase sound model available for the given keyphrase ID and the
      * user ID of the caller.
@@ -106,6 +121,7 @@
      * @param bcp47Locale The BCP47 language tag  for the keyphrase's locale.
      */
     boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale);
+
     /**
      * Generates KeyphraseMetadata for an enrolled sound model based on keyphrase string, locale,
      * and the user ID of the caller.
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index e47c335..73914a2 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.app;
 
-import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE;
 import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_TITLE;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -87,17 +86,13 @@
                         mTelecomManager.getDefaultDialerPackage(UserHandle.of(mUserId))));
 
         final AlertDialog.Builder builder;
-        final String dialogMessage;
         if (showEmergencyCallButton) {
             builder = new AlertDialog.Builder(this, R.style.AlertDialogWithEmergencyButton);
-            dialogMessage = getDialogMessage(R.string.work_mode_dialer_off_message);
             builder.setNeutralButton(R.string.work_mode_emergency_call_button, this);
         } else {
             builder = new AlertDialog.Builder(this);
-            dialogMessage = getDialogMessage(R.string.work_mode_off_message);
         }
         builder.setTitle(getDialogTitle())
-                .setMessage(dialogMessage)
                 .setOnDismissListener(this)
                 .setPositiveButton(R.string.work_mode_turn_on, this)
                 .setNegativeButton(R.string.cancel, null);
@@ -120,12 +115,6 @@
                 UNLAUNCHABLE_APP_WORK_PAUSED_TITLE, () -> getString(R.string.work_mode_off_title));
     }
 
-    private String getDialogMessage(int dialogMessageString) {
-        return getSystemService(DevicePolicyManager.class).getResources().getString(
-                UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE,
-                () -> getString(dialogMessageString));
-    }
-
     @Override
     public void onDismiss(DialogInterface dialog) {
         finish();
diff --git a/core/java/com/android/internal/util/DumpUtils.java b/core/java/com/android/internal/util/DumpUtils.java
index f6d80a5..8fe2b9c 100644
--- a/core/java/com/android/internal/util/DumpUtils.java
+++ b/core/java/com/android/internal/util/DumpUtils.java
@@ -25,6 +25,7 @@
 import android.os.Handler;
 import android.text.TextUtils;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -312,5 +313,85 @@
                     || cn.flattenToString().toLowerCase().contains(filterString.toLowerCase());
         };
     }
-}
 
+    /**
+     * Lambda used to dump a key (and its index) while iterating though a collection.
+     */
+    public interface KeyDumper {
+
+        /** Dumps the index and key.*/
+        void dump(int index, int key);
+    }
+
+    /**
+     * Lambda used to dump a value while iterating though a collection.
+     *
+     * @param <T> type of the value.
+     */
+    public interface ValueDumper<T> {
+
+        /** Dumps the value.*/
+        void dump(T value);
+    }
+
+    /**
+     * Dumps a sparse array.
+     */
+    public static void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<?> array,
+            String name) {
+        dumpSparseArray(pw, prefix, array, name, /* keyDumper= */ null, /* valueDumper= */ null);
+    }
+
+    /**
+     * Dumps the values of a sparse array.
+     */
+    public static <T> void dumpSparseArrayValues(PrintWriter pw, String prefix,
+            SparseArray<T> array, String name) {
+        dumpSparseArray(pw, prefix, array, name, (i, k) -> {
+            pw.printf("%s%s", prefix, prefix);
+        }, /* valueDumper= */ null);
+    }
+
+    /**
+     * Dumps a sparse array, customizing each line.
+     */
+    public static <T> void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<T> array,
+            String name, @Nullable KeyDumper keyDumper, @Nullable ValueDumper<T> valueDumper) {
+        int size = array.size();
+        if (size == 0) {
+            pw.print(prefix);
+            pw.print("No ");
+            pw.print(name);
+            pw.println("s");
+            return;
+        }
+        pw.print(prefix);
+        pw.print(size);
+        pw.print(' ');
+        pw.print(name);
+        pw.println("(s):");
+
+        String prefix2 = prefix + prefix;
+        for (int i = 0; i < size; i++) {
+            int key = array.keyAt(i);
+            T value = array.valueAt(i);
+            if (keyDumper != null) {
+                keyDumper.dump(i, key);
+            } else {
+                pw.print(prefix2);
+                pw.print(i);
+                pw.print(": ");
+                pw.print(key);
+                pw.print("->");
+            }
+            if (value == null) {
+                pw.print("(null)");
+            } else if (valueDumper != null) {
+                valueDumper.dump(value);
+            } else {
+                pw.print(value);
+            }
+            pw.println();
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index c3e2fcd..0dc9712 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -80,6 +80,7 @@
     private static final int DOT_ACTIVATION_DURATION_MILLIS = 50;
     private static final int DOT_RADIUS_INCREASE_DURATION_MILLIS = 96;
     private static final int DOT_RADIUS_DECREASE_DURATION_MILLIS = 192;
+    private static final int ALPHA_MAX_VALUE = 255;
     private static final float MIN_DOT_HIT_FACTOR = 0.2f;
     private final CellState[][] mCellStates;
 
@@ -92,6 +93,8 @@
     private final int mPathWidth;
     private final int mLineFadeOutAnimationDurationMs;
     private final int mLineFadeOutAnimationDelayMs;
+    private final int mFadePatternAnimationDurationMs;
+    private final int mFadePatternAnimationDelayMs;
 
     private boolean mDrawingProfilingStarted = false;
 
@@ -148,6 +151,10 @@
     private boolean mPatternInProgress = false;
     private boolean mFadePattern = true;
 
+    private boolean mFadeClear = false;
+    private int mFadeAnimationAlpha = ALPHA_MAX_VALUE;
+    private final Path mPatternPath = new Path();
+
     @UnsupportedAppUsage
     private float mSquareWidth;
     @UnsupportedAppUsage
@@ -169,6 +176,7 @@
 
     private final Interpolator mFastOutSlowInInterpolator;
     private final Interpolator mLinearOutSlowInInterpolator;
+    private final Interpolator mStandardAccelerateInterpolator;
     private final PatternExploreByTouchHelper mExploreByTouchHelper;
 
     private Drawable mSelectedDrawable;
@@ -356,6 +364,11 @@
         mLineFadeOutAnimationDelayMs =
             getResources().getInteger(R.integer.lock_pattern_line_fade_out_delay);
 
+        mFadePatternAnimationDurationMs =
+                getResources().getInteger(R.integer.lock_pattern_fade_pattern_duration);
+        mFadePatternAnimationDelayMs =
+                getResources().getInteger(R.integer.lock_pattern_fade_pattern_delay);
+
         mDotSize = getResources().getDimensionPixelSize(R.dimen.lock_pattern_dot_size);
         mDotSizeActivated = getResources().getDimensionPixelSize(
                 R.dimen.lock_pattern_dot_size_activated);
@@ -386,6 +399,8 @@
                 AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
         mLinearOutSlowInInterpolator =
                 AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
+        mStandardAccelerateInterpolator =
+                AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_linear_in);
         mExploreByTouchHelper = new PatternExploreByTouchHelper(this);
         setAccessibilityDelegate(mExploreByTouchHelper);
 
@@ -626,6 +641,15 @@
         resetPattern();
     }
 
+    /**
+     * Clear the pattern by fading it out.
+     */
+    @UnsupportedAppUsage
+    public void fadeClearPattern() {
+        mFadeClear = true;
+        startFadePatternAnimation();
+    }
+
     @Override
     protected boolean dispatchHoverEvent(MotionEvent event) {
         // Dispatch to onHoverEvent first so mPatternInProgress is up to date when the
@@ -643,6 +667,7 @@
             resetLastActivatedCellProgress();
         }
         mPattern.clear();
+        mPatternPath.reset();
         clearPatternDrawLookup();
         mPatternDisplayMode = DisplayMode.Correct;
         invalidate();
@@ -815,6 +840,33 @@
         notifyCellAdded();
     }
 
+    private void startFadePatternAnimation() {
+        AnimatorSet animatorSet = new AnimatorSet();
+        animatorSet.play(createFadePatternAnimation());
+        animatorSet.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mFadeAnimationAlpha = ALPHA_MAX_VALUE;
+                mFadeClear = false;
+                resetPattern();
+            }
+        });
+        animatorSet.start();
+
+    }
+
+    private Animator createFadePatternAnimation() {
+        ValueAnimator valueAnimator = ValueAnimator.ofInt(ALPHA_MAX_VALUE, 0);
+        valueAnimator.addUpdateListener(animation -> {
+            mFadeAnimationAlpha = (int) animation.getAnimatedValue();
+            invalidate();
+        });
+        valueAnimator.setInterpolator(mStandardAccelerateInterpolator);
+        valueAnimator.setStartDelay(mFadePatternAnimationDelayMs);
+        valueAnimator.setDuration(mFadePatternAnimationDurationMs);
+        return valueAnimator;
+    }
+
     private void startCellActivatedAnimation(Cell cell) {
         startCellActivationAnimation(cell, CELL_ACTIVATE);
     }
@@ -1247,14 +1299,14 @@
         // draw the path of the pattern (unless we are in stealth mode)
         final boolean drawPath = !mInStealthMode;
 
-        if (drawPath) {
+        if (drawPath && !mFadeClear) {
             mPathPaint.setColor(getCurrentColor(true /* partOfPattern */));
 
             boolean anyCircles = false;
             float lastX = 0f;
             float lastY = 0f;
             long elapsedRealtime = SystemClock.elapsedRealtime();
-           for (int i = 0; i < count; i++) {
+            for (int i = 0; i < count; i++) {
                 Cell cell = pattern.get(i);
 
                 // only draw the part of the pattern stored in
@@ -1285,6 +1337,11 @@
                     }
                     drawLineSegment(canvas, /* startX = */ lastX, /* startY = */ lastY, endX, endY,
                             mLineFadeStart[i], elapsedRealtime);
+
+                    Path tempPath = new Path();
+                    tempPath.moveTo(lastX, lastY);
+                    tempPath.lineTo(centerX, centerY);
+                    mPatternPath.addPath(tempPath);
                 }
                 lastX = centerX;
                 lastY = centerY;
@@ -1303,6 +1360,11 @@
             }
         }
 
+        if (mFadeClear) {
+            mPathPaint.setAlpha(mFadeAnimationAlpha);
+            canvas.drawPath(mPatternPath, mPathPaint);
+        }
+
         // draw the circles
         for (int i = 0; i < 3; i++) {
             float centerY = getCenterYForRow(i);
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 410b441..dd72689 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -48,22 +48,12 @@
 
     struct {
         jclass clazz;
-
         jmethodID init;
-
-        jfieldID vsyncId;
-        jfieldID expectedPresentationTime;
-        jfieldID deadline;
     } frameTimelineClassInfo;
 
     struct {
         jclass clazz;
-
         jmethodID init;
-
-        jfieldID frameInterval;
-        jfieldID preferredFrameTimelineIndex;
-        jfieldID frameTimelines;
     } vsyncEventDataClassInfo;
 
 } gDisplayEventReceiverClassInfo;
@@ -71,7 +61,7 @@
 
 class NativeDisplayEventReceiver : public DisplayEventDispatcher {
 public:
-    NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak, jobject vsyncEventDataWeak,
+    NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
                                const sp<MessageQueue>& messageQueue, jint vsyncSource,
                                jint eventRegistration, jlong layerHandle);
 
@@ -82,7 +72,6 @@
 
 private:
     jobject mReceiverWeakGlobal;
-    jobject mVsyncEventDataWeakGlobal;
     sp<MessageQueue> mMessageQueue;
 
     void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
@@ -96,7 +85,6 @@
 };
 
 NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
-                                                       jobject vsyncEventDataWeak,
                                                        const sp<MessageQueue>& messageQueue,
                                                        jint vsyncSource, jint eventRegistration,
                                                        jlong layerHandle)
@@ -108,7 +96,6 @@
                                                           reinterpret_cast<IBinder*>(layerHandle))
                                                 : nullptr),
         mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
-        mVsyncEventDataWeakGlobal(env->NewGlobalRef(vsyncEventDataWeak)),
         mMessageQueue(messageQueue) {
     ALOGV("receiver %p ~ Initializing display event receiver.", this);
 }
@@ -167,43 +154,12 @@
     JNIEnv* env = AndroidRuntime::getJNIEnv();
 
     ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal));
-    ScopedLocalRef<jobject> vsyncEventDataObj(env, GetReferent(env, mVsyncEventDataWeakGlobal));
-    if (receiverObj.get() && vsyncEventDataObj.get()) {
+    if (receiverObj.get()) {
         ALOGV("receiver %p ~ Invoking vsync handler.", this);
 
-        env->SetIntField(vsyncEventDataObj.get(),
-                         gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo
-                                 .preferredFrameTimelineIndex,
-                         vsyncEventData.preferredFrameTimelineIndex);
-        env->SetLongField(vsyncEventDataObj.get(),
-                          gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval,
-                          vsyncEventData.frameInterval);
-
-        ScopedLocalRef<jobjectArray>
-                frameTimelinesObj(env,
-                                  reinterpret_cast<jobjectArray>(
-                                          env->GetObjectField(vsyncEventDataObj.get(),
-                                                              gDisplayEventReceiverClassInfo
-                                                                      .vsyncEventDataClassInfo
-                                                                      .frameTimelines)));
-        for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
-            VsyncEventData::FrameTimeline& frameTimeline = vsyncEventData.frameTimelines[i];
-            ScopedLocalRef<jobject>
-                    frameTimelineObj(env, env->GetObjectArrayElement(frameTimelinesObj.get(), i));
-            env->SetLongField(frameTimelineObj.get(),
-                              gDisplayEventReceiverClassInfo.frameTimelineClassInfo.vsyncId,
-                              frameTimeline.vsyncId);
-            env->SetLongField(frameTimelineObj.get(),
-                              gDisplayEventReceiverClassInfo.frameTimelineClassInfo
-                                      .expectedPresentationTime,
-                              frameTimeline.expectedPresentationTime);
-            env->SetLongField(frameTimelineObj.get(),
-                              gDisplayEventReceiverClassInfo.frameTimelineClassInfo.deadline,
-                              frameTimeline.deadlineTimestamp);
-        }
-
+        jobject javaVsyncEventData = createJavaVsyncEventData(env, vsyncEventData);
         env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync,
-                            timestamp, displayId.value, count);
+                            timestamp, displayId.value, count, javaVsyncEventData);
         ALOGV("receiver %p ~ Returned from vsync handler.", this);
     }
 
@@ -271,9 +227,8 @@
     mMessageQueue->raiseAndClearException(env, "dispatchModeChanged");
 }
 
-static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject vsyncEventDataWeak,
-                        jobject messageQueueObj, jint vsyncSource, jint eventRegistration,
-                        jlong layerHandle) {
+static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject messageQueueObj,
+                        jint vsyncSource, jint eventRegistration, jlong layerHandle) {
     sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
     if (messageQueue == NULL) {
         jniThrowRuntimeException(env, "MessageQueue is not initialized.");
@@ -281,8 +236,8 @@
     }
 
     sp<NativeDisplayEventReceiver> receiver =
-            new NativeDisplayEventReceiver(env, receiverWeak, vsyncEventDataWeak, messageQueue,
-                                           vsyncSource, eventRegistration, layerHandle);
+            new NativeDisplayEventReceiver(env, receiverWeak, messageQueue, vsyncSource,
+                                           eventRegistration, layerHandle);
     status_t status = receiver->initialize();
     if (status) {
         String8 message;
@@ -329,9 +284,7 @@
 
 static const JNINativeMethod gMethods[] = {
         /* name, signature, funcPtr */
-        {"nativeInit",
-         "(Ljava/lang/ref/WeakReference;Ljava/lang/ref/WeakReference;Landroid/os/"
-         "MessageQueue;IIJ)J",
+        {"nativeInit", "(Ljava/lang/ref/WeakReference;Landroid/os/MessageQueue;IIJ)J",
          (void*)nativeInit},
         {"nativeGetDisplayEventReceiverFinalizer", "()J",
          (void*)nativeGetDisplayEventReceiverFinalizer},
@@ -348,7 +301,8 @@
     gDisplayEventReceiverClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
 
     gDisplayEventReceiverClassInfo.dispatchVsync =
-            GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", "(JJI)V");
+            GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync",
+                             "(JJILandroid/view/DisplayEventReceiver$VsyncEventData;)V");
     gDisplayEventReceiverClassInfo.dispatchHotplug = GetMethodIDOrDie(env,
             gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V");
     gDisplayEventReceiverClassInfo.dispatchModeChanged =
@@ -374,15 +328,6 @@
     gDisplayEventReceiverClassInfo.frameTimelineClassInfo.init =
             GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz,
                              "<init>", "(JJJ)V");
-    gDisplayEventReceiverClassInfo.frameTimelineClassInfo.vsyncId =
-            GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz,
-                            "vsyncId", "J");
-    gDisplayEventReceiverClassInfo.frameTimelineClassInfo.expectedPresentationTime =
-            GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz,
-                            "expectedPresentationTime", "J");
-    gDisplayEventReceiverClassInfo.frameTimelineClassInfo.deadline =
-            GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz,
-                            "deadline", "J");
 
     jclass vsyncEventDataClazz =
             FindClassOrDie(env, "android/view/DisplayEventReceiver$VsyncEventData");
@@ -394,17 +339,6 @@
                              "([Landroid/view/"
                              "DisplayEventReceiver$VsyncEventData$FrameTimeline;IJ)V");
 
-    gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.preferredFrameTimelineIndex =
-            GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
-                            "preferredFrameTimelineIndex", "I");
-    gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval =
-            GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
-                            "frameInterval", "J");
-    gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameTimelines =
-            GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
-                            "frameTimelines",
-                            "[Landroid/view/DisplayEventReceiver$VsyncEventData$FrameTimeline;");
-
     return res;
 }
 
diff --git a/core/proto/android/app/notification_channel.proto b/core/proto/android/app/notification_channel.proto
index c835b90..d79de5c 100644
--- a/core/proto/android/app/notification_channel.proto
+++ b/core/proto/android/app/notification_channel.proto
@@ -56,7 +56,9 @@
     optional android.media.AudioAttributesProto audio_attributes = 16;
     // If this is a blockable system notification channel.
     optional bool is_blockable_system = 17;
-    optional bool fg_service_shown = 18;
+    // On U+, this field will be true if either a foreground service or a user initiated job is
+    // shown whereas on T-, this field will only be true if a foreground service is shown.
+    optional bool user_visible_task_shown = 18;
     // Default is true.
     // Allows the notifications to appear outside of the shade in floating windows
     optional bool allow_app_overlay = 19;
diff --git a/core/proto/android/server/windowmanagertransitiontrace.proto b/core/proto/android/server/windowmanagertransitiontrace.proto
index 9e53a91..25985eb 100644
--- a/core/proto/android/server/windowmanagertransitiontrace.proto
+++ b/core/proto/android/server/windowmanagertransitiontrace.proto
@@ -23,7 +23,7 @@
 option java_multiple_files = true;
 
 /* Represents a file full of transition entries.
-   Encoded, it should start with 0x9 0x57 0x49 0x4e 0x54 0x52 0x41 0x43 0x45 (.TRNTRACE), such
+   Encoded, it should start with 0x09 0x54 0x52 0x4E 0x54 0x52 0x41 0x43 0x45 (TRNTRACE), such
    that it can be easily identified. */
 message TransitionTraceProto {
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3d72aef..eff45d3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1746,37 +1746,6 @@
         android:protectionLevel="dangerous"
         android:permissionFlags="hardRestricted" />
 
-    <!-- @TestApi Allows an application to access wrist temperature data from the watch sensors.
-        <p class="note"><strong>Note: </strong> This permission is for Wear OS only.
-        <p>Protection level: dangerous
-        @hide
-        -->
-    <permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE"
-                android:permissionGroup="android.permission-group.UNDEFINED"
-                android:label="@string/permlab_bodySensorsWristTemperature"
-                android:description="@string/permdesc_bodySensorsWristTemperature"
-                android:backgroundPermission="android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND"
-                android:protectionLevel="dangerous" />
-
-    <!-- @TestApi Allows an application to access wrist temperature data from the watch sensors.
-         If you're requesting this permission, you must also request
-         {@link #BODY_SENSORS_WRIST_TEMPERATURE}. Requesting this permission by itself doesn't
-         give you wrist temperature body sensors access.
-         <p class="note"><strong>Note: </strong> This permission is for Wear OS only.
-         <p>Protection level: dangerous
-
-         <p> This is a hard restricted permission which cannot be held by an app until
-         the installer on record allowlists the permission. For more details see
-         {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
-         @hide
-    -->
-    <permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND"
-                android:permissionGroup="android.permission-group.UNDEFINED"
-                android:label="@string/permlab_bodySensors_wristTemperature_background"
-                android:description="@string/permdesc_bodySensors_wristTemperature_background"
-                android:protectionLevel="dangerous"
-                android:permissionFlags="hardRestricted" />
-
     <!-- Allows an app to use fingerprint hardware.
          <p>Protection level: normal
          @deprecated Applications should request {@link
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index b1b1edf..ecf196a 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -685,6 +685,9 @@
     <!-- Parameters applied to line disappearing animation in LockPatternView in milliseconds. -->
     <integer name="lock_pattern_line_fade_out_duration">500</integer>
     <integer name="lock_pattern_line_fade_out_delay">150</integer>
+    <!-- Parameters applied to fade pattern animation in LockPatternView in milliseconds. -->
+    <integer name="lock_pattern_fade_pattern_duration">200</integer>
+    <integer name="lock_pattern_fade_pattern_delay">2300</integer>
 
     <dimen name="text_handle_min_size">40dp</dimen>
 
diff --git a/core/res/res/values/public-final.xml b/core/res/res/values/public-final.xml
index 85325fe..daa0f553 100644
--- a/core/res/res/values/public-final.xml
+++ b/core/res/res/values/public-final.xml
@@ -3402,4 +3402,343 @@
     <!-- @hide @SystemApi -->
   <public type="bool" name="config_enableQrCodeScannerOnLockScreen" id="0x01110008" />
 
+  <!-- ===============================================================
+    Resources added in version U of the platform
+
+    NOTE: After this version of the platform is forked, changes cannot be made to the root
+    branch's groups for that release. Only merge changes to the forked platform branch.
+    =============================================================== -->
+  <eat-comment/>
+
+  <staging-public-group-final type="attr" first-id="0x01ce0000">
+    <public name="handwritingBoundsOffsetLeft" />
+    <public name="handwritingBoundsOffsetTop" />
+    <public name="handwritingBoundsOffsetRight" />
+    <public name="handwritingBoundsOffsetBottom" />
+    <public name="accessibilityDataSensitive" />
+    <public name="enableTextStylingShortcuts" />
+    <public name="requiredDisplayCategory"/>
+    <public name="removed_maxConcurrentSessionsCount" />
+    <public name="visualQueryDetectionService" />
+    <public name="physicalKeyboardHintLanguageTag" />
+    <public name="physicalKeyboardHintLayoutType" />
+    <public name="allowSharedIsolatedProcess" />
+    <public name="keyboardLocale" />
+    <public name="keyboardLayoutType" />
+    <public name="allowUpdateOwnership" />
+    <public name="isCredential"/>
+    <public name="searchResultHighlightColor" />
+    <public name="focusedSearchResultHighlightColor" />
+    <public name="stylusHandwritingSettingsActivity" />
+    <public name="windowNoMoveAnimation" />
+    <public name="settingsSubtitle" />
+    <public name="capability" />
+  </staging-public-group-final>
+
+  <public type="attr" name="handwritingBoundsOffsetLeft" id="0x01010673" />
+  <public type="attr" name="handwritingBoundsOffsetTop" id="0x01010674" />
+  <public type="attr" name="handwritingBoundsOffsetRight" id="0x01010675" />
+  <public type="attr" name="handwritingBoundsOffsetBottom" id="0x01010676" />
+  <public type="attr" name="accessibilityDataSensitive" id="0x01010677" />
+  <public type="attr" name="enableTextStylingShortcuts" id="0x01010678" />
+  <public type="attr" name="requiredDisplayCategory" id="0x01010679" />
+  <public type="attr" name="visualQueryDetectionService" id="0x0101067a" />
+  <public type="attr" name="physicalKeyboardHintLanguageTag" id="0x0101067b" />
+  <public type="attr" name="physicalKeyboardHintLayoutType" id="0x0101067c" />
+  <public type="attr" name="allowSharedIsolatedProcess" id="0x0101067d" />
+  <public type="attr" name="keyboardLocale" id="0x0101067e" />
+  <public type="attr" name="keyboardLayoutType" id="0x0101067f" />
+  <public type="attr" name="allowUpdateOwnership" id="0x01010680" />
+  <public type="attr" name="isCredential" id="0x01010681" />
+  <public type="attr" name="searchResultHighlightColor" id="0x01010682" />
+  <public type="attr" name="focusedSearchResultHighlightColor" id="0x01010683" />
+  <public type="attr" name="stylusHandwritingSettingsActivity" id="0x01010684" />
+  <public type="attr" name="windowNoMoveAnimation" id="0x01010685" />
+  <public type="attr" name="settingsSubtitle" id="0x01010686" />
+  <public type="attr" name="capability" id="0x01010687" />
+
+  <staging-public-group-final type="id" first-id="0x01cd0000">
+    <public name="bold" />
+    <public name="italic" />
+    <public name="underline" />
+    <public name="accessibilityActionScrollInDirection" />
+  </staging-public-group-final>
+
+  <public type="id" name="bold" id="0x0102005b" />
+  <public type="id" name="italic" id="0x0102005c" />
+  <public type="id" name="underline" id="0x0102005d" />
+  <public type="id" name="accessibilityActionScrollInDirection" id="0x0102005e" />
+
+  <staging-public-group-final type="string" first-id="0x01cb0000">
+    <!-- @hide @SystemApi -->
+    <public name="config_systemWearHealthService" />
+    <!-- @hide @SystemApi -->
+    <public name="config_defaultNotes" />
+    <!-- @hide @SystemApi -->
+    <public name="config_systemFinancedDeviceController" />
+    <!-- @hide @SystemApi -->
+    <public name="config_systemCallStreaming" />
+  </staging-public-group-final>
+
+    <!-- @hide @SystemApi -->
+  <public type="string" name="config_systemWearHealthService" id="0x01040044" />
+    <!-- @hide @SystemApi -->
+  <public type="string" name="config_defaultNotes" id="0x01040045" />
+    <!-- @hide @SystemApi -->
+  <public type="string" name="config_systemFinancedDeviceController" id="0x01040046" />
+    <!-- @hide @SystemApi -->
+  <public type="string" name="config_systemCallStreaming" id="0x01040047" />
+
+  <staging-public-group-final type="dimen" first-id="0x01ca0000">
+    <!-- @hide @SystemApi -->
+    <public name="config_viewConfigurationHandwritingGestureLineMargin" />
+  </staging-public-group-final>
+
+    <!-- @hide @SystemApi -->
+  <public type="dimen" name="config_viewConfigurationHandwritingGestureLineMargin" id="0x0105000a" />
+
+  <staging-public-group-final type="color" first-id="0x01c90000">
+    <public name="system_primary_container_light" />
+    <public name="system_on_primary_container_light" />
+    <public name="system_primary_light" />
+    <public name="system_on_primary_light" />
+    <public name="system_secondary_container_light" />
+    <public name="system_on_secondary_container_light" />
+    <public name="system_secondary_light" />
+    <public name="system_on_secondary_light" />
+    <public name="system_tertiary_container_light" />
+    <public name="system_on_tertiary_container_light" />
+    <public name="system_tertiary_light" />
+    <public name="system_on_tertiary_light" />
+    <public name="system_background_light" />
+    <public name="system_on_background_light" />
+    <public name="system_surface_light" />
+    <public name="system_on_surface_light" />
+    <public name="system_surface_container_low_light" />
+    <public name="system_surface_container_lowest_light" />
+    <public name="system_surface_container_light" />
+    <public name="system_surface_container_high_light" />
+    <public name="system_surface_container_highest_light" />
+    <public name="system_surface_bright_light" />
+    <public name="system_surface_dim_light" />
+    <public name="system_surface_variant_light" />
+    <public name="system_on_surface_variant_light" />
+    <public name="system_outline_light" />
+    <public name="system_error_light" />
+    <public name="system_on_error_light" />
+    <public name="system_error_container_light" />
+    <public name="system_on_error_container_light" />
+    <public name="removed_system_primary_fixed_light" />
+    <public name="removed_system_primary_fixed_dim_light" />
+    <public name="removed_system_on_primary_fixed_light" />
+    <public name="removed_system_on_primary_fixed_variant_light" />
+    <public name="removed_system_secondary_fixed_light" />
+    <public name="removed_system_secondary_fixed_dim_light" />
+    <public name="removed_system_on_secondary_fixed_light" />
+    <public name="removed_system_on_secondary_fixed_variant_light" />
+    <public name="removed_system_tertiary_fixed_light" />
+    <public name="removed_system_tertiary_fixed_dim_light" />
+    <public name="removed_system_on_tertiary_fixed_light" />
+    <public name="removed_system_on_tertiary_fixed_variant_light" />
+    <public name="system_control_activated_light" />
+    <public name="system_control_normal_light" />
+    <public name="system_control_highlight_light" />
+    <public name="system_text_primary_inverse_light" />
+    <public name="system_text_secondary_and_tertiary_inverse_light" />
+    <public name="system_text_primary_inverse_disable_only_light" />
+    <public name="system_text_secondary_and_tertiary_inverse_disabled_light" />
+    <public name="system_text_hint_inverse_light" />
+    <public name="system_palette_key_color_primary_light" />
+    <public name="system_palette_key_color_secondary_light" />
+    <public name="system_palette_key_color_tertiary_light" />
+    <public name="system_palette_key_color_neutral_light" />
+    <public name="system_palette_key_color_neutral_variant_light" />
+    <public name="system_primary_container_dark"/>
+    <public name="system_on_primary_container_dark"/>
+    <public name="system_primary_dark"/>
+    <public name="system_on_primary_dark"/>
+    <public name="system_secondary_container_dark"/>
+    <public name="system_on_secondary_container_dark"/>
+    <public name="system_secondary_dark"/>
+    <public name="system_on_secondary_dark"/>
+    <public name="system_tertiary_container_dark"/>
+    <public name="system_on_tertiary_container_dark"/>
+    <public name="system_tertiary_dark"/>
+    <public name="system_on_tertiary_dark"/>
+    <public name="system_background_dark"/>
+    <public name="system_on_background_dark"/>
+    <public name="system_surface_dark"/>
+    <public name="system_on_surface_dark"/>
+    <public name="system_surface_container_low_dark"/>
+    <public name="system_surface_container_lowest_dark"/>
+    <public name="system_surface_container_dark"/>
+    <public name="system_surface_container_high_dark"/>
+    <public name="system_surface_container_highest_dark"/>
+    <public name="system_surface_bright_dark"/>
+    <public name="system_surface_dim_dark"/>
+    <public name="system_surface_variant_dark"/>
+    <public name="system_on_surface_variant_dark"/>
+    <public name="system_outline_dark"/>
+    <public name="system_error_dark"/>
+    <public name="system_on_error_dark"/>
+    <public name="system_error_container_dark"/>
+    <public name="system_on_error_container_dark"/>
+    <public name="removed_system_primary_fixed_dark"/>
+    <public name="removed_system_primary_fixed_dim_dark"/>
+    <public name="removed_system_on_primary_fixed_dark"/>
+    <public name="removed_system_on_primary_fixed_variant_dark"/>
+    <public name="removed_system_secondary_fixed_dark"/>
+    <public name="removed_system_secondary_fixed_dim_dark"/>
+    <public name="removed_system_on_secondary_fixed_dark"/>
+    <public name="removed_system_on_secondary_fixed_variant_dark"/>
+    <public name="removed_system_tertiary_fixed_dark"/>
+    <public name="removed_system_tertiary_fixed_dim_dark"/>
+    <public name="removed_system_on_tertiary_fixed_dark"/>
+    <public name="removed_system_on_tertiary_fixed_variant_dark"/>
+    <public name="system_control_activated_dark"/>
+    <public name="system_control_normal_dark"/>
+    <public name="system_control_highlight_dark"/>
+    <public name="system_text_primary_inverse_dark"/>
+    <public name="system_text_secondary_and_tertiary_inverse_dark"/>
+    <public name="system_text_primary_inverse_disable_only_dark"/>
+    <public name="system_text_secondary_and_tertiary_inverse_disabled_dark"/>
+    <public name="system_text_hint_inverse_dark"/>
+    <public name="system_palette_key_color_primary_dark"/>
+    <public name="system_palette_key_color_secondary_dark"/>
+    <public name="system_palette_key_color_tertiary_dark"/>
+    <public name="system_palette_key_color_neutral_dark"/>
+    <public name="system_palette_key_color_neutral_variant_dark"/>
+    <public name="system_primary_fixed" />
+    <public name="system_primary_fixed_dim" />
+    <public name="system_on_primary_fixed" />
+    <public name="system_on_primary_fixed_variant" />
+    <public name="system_secondary_fixed" />
+    <public name="system_secondary_fixed_dim" />
+    <public name="system_on_secondary_fixed" />
+    <public name="system_on_secondary_fixed_variant" />
+    <public name="system_tertiary_fixed" />
+    <public name="system_tertiary_fixed_dim" />
+    <public name="system_on_tertiary_fixed" />
+    <public name="system_on_tertiary_fixed_variant" />
+    <public name="system_outline_variant_light" />
+    <public name="system_outline_variant_dark" />
+  </staging-public-group-final>
+
+  <public type="color" name="system_primary_container_light" id="0x0106005e" />
+  <public type="color" name="system_on_primary_container_light" id="0x0106005f" />
+  <public type="color" name="system_primary_light" id="0x01060060" />
+  <public type="color" name="system_on_primary_light" id="0x01060061" />
+  <public type="color" name="system_secondary_container_light" id="0x01060062" />
+  <public type="color" name="system_on_secondary_container_light" id="0x01060063" />
+  <public type="color" name="system_secondary_light" id="0x01060064" />
+  <public type="color" name="system_on_secondary_light" id="0x01060065" />
+  <public type="color" name="system_tertiary_container_light" id="0x01060066" />
+  <public type="color" name="system_on_tertiary_container_light" id="0x01060067" />
+  <public type="color" name="system_tertiary_light" id="0x01060068" />
+  <public type="color" name="system_on_tertiary_light" id="0x01060069" />
+  <public type="color" name="system_background_light" id="0x0106006a" />
+  <public type="color" name="system_on_background_light" id="0x0106006b" />
+  <public type="color" name="system_surface_light" id="0x0106006c" />
+  <public type="color" name="system_on_surface_light" id="0x0106006d" />
+  <public type="color" name="system_surface_container_low_light" id="0x0106006e" />
+  <public type="color" name="system_surface_container_lowest_light" id="0x0106006f" />
+  <public type="color" name="system_surface_container_light" id="0x01060070" />
+  <public type="color" name="system_surface_container_high_light" id="0x01060071" />
+  <public type="color" name="system_surface_container_highest_light" id="0x01060072" />
+  <public type="color" name="system_surface_bright_light" id="0x01060073" />
+  <public type="color" name="system_surface_dim_light" id="0x01060074" />
+  <public type="color" name="system_surface_variant_light" id="0x01060075" />
+  <public type="color" name="system_on_surface_variant_light" id="0x01060076" />
+  <public type="color" name="system_outline_light" id="0x01060077" />
+  <public type="color" name="system_error_light" id="0x01060078" />
+  <public type="color" name="system_on_error_light" id="0x01060079" />
+  <public type="color" name="system_error_container_light" id="0x0106007a" />
+  <public type="color" name="system_on_error_container_light" id="0x0106007b" />
+  <public type="color" name="system_control_activated_light" id="0x0106007c" />
+  <public type="color" name="system_control_normal_light" id="0x0106007d" />
+  <public type="color" name="system_control_highlight_light" id="0x0106007e" />
+  <public type="color" name="system_text_primary_inverse_light" id="0x0106007f" />
+  <public type="color" name="system_text_secondary_and_tertiary_inverse_light" id="0x01060080" />
+  <public type="color" name="system_text_primary_inverse_disable_only_light" id="0x01060081" />
+  <public type="color" name="system_text_secondary_and_tertiary_inverse_disabled_light" id="0x01060082" />
+  <public type="color" name="system_text_hint_inverse_light" id="0x01060083" />
+  <public type="color" name="system_palette_key_color_primary_light" id="0x01060084" />
+  <public type="color" name="system_palette_key_color_secondary_light" id="0x01060085" />
+  <public type="color" name="system_palette_key_color_tertiary_light" id="0x01060086" />
+  <public type="color" name="system_palette_key_color_neutral_light" id="0x01060087" />
+  <public type="color" name="system_palette_key_color_neutral_variant_light" id="0x01060088" />
+  <public type="color" name="system_primary_container_dark" id="0x01060089" />
+  <public type="color" name="system_on_primary_container_dark" id="0x0106008a" />
+  <public type="color" name="system_primary_dark" id="0x0106008b" />
+  <public type="color" name="system_on_primary_dark" id="0x0106008c" />
+  <public type="color" name="system_secondary_container_dark" id="0x0106008d" />
+  <public type="color" name="system_on_secondary_container_dark" id="0x0106008e" />
+  <public type="color" name="system_secondary_dark" id="0x0106008f" />
+  <public type="color" name="system_on_secondary_dark" id="0x01060090" />
+  <public type="color" name="system_tertiary_container_dark" id="0x01060091" />
+  <public type="color" name="system_on_tertiary_container_dark" id="0x01060092" />
+  <public type="color" name="system_tertiary_dark" id="0x01060093" />
+  <public type="color" name="system_on_tertiary_dark" id="0x01060094" />
+  <public type="color" name="system_background_dark" id="0x01060095" />
+  <public type="color" name="system_on_background_dark" id="0x01060096" />
+  <public type="color" name="system_surface_dark" id="0x01060097" />
+  <public type="color" name="system_on_surface_dark" id="0x01060098" />
+  <public type="color" name="system_surface_container_low_dark" id="0x01060099" />
+  <public type="color" name="system_surface_container_lowest_dark" id="0x0106009a" />
+  <public type="color" name="system_surface_container_dark" id="0x0106009b" />
+  <public type="color" name="system_surface_container_high_dark" id="0x0106009c" />
+  <public type="color" name="system_surface_container_highest_dark" id="0x0106009d" />
+  <public type="color" name="system_surface_bright_dark" id="0x0106009e" />
+  <public type="color" name="system_surface_dim_dark" id="0x0106009f" />
+  <public type="color" name="system_surface_variant_dark" id="0x010600a0" />
+  <public type="color" name="system_on_surface_variant_dark" id="0x010600a1" />
+  <public type="color" name="system_outline_dark" id="0x010600a2" />
+  <public type="color" name="system_error_dark" id="0x010600a3" />
+  <public type="color" name="system_on_error_dark" id="0x010600a4" />
+  <public type="color" name="system_error_container_dark" id="0x010600a5" />
+  <public type="color" name="system_on_error_container_dark" id="0x010600a6" />
+  <public type="color" name="system_control_activated_dark" id="0x010600a7" />
+  <public type="color" name="system_control_normal_dark" id="0x010600a8" />
+  <public type="color" name="system_control_highlight_dark" id="0x010600a9" />
+  <public type="color" name="system_text_primary_inverse_dark" id="0x010600aa" />
+  <public type="color" name="system_text_secondary_and_tertiary_inverse_dark" id="0x010600ab" />
+  <public type="color" name="system_text_primary_inverse_disable_only_dark" id="0x010600ac" />
+  <public type="color" name="system_text_secondary_and_tertiary_inverse_disabled_dark" id="0x010600ad" />
+  <public type="color" name="system_text_hint_inverse_dark" id="0x010600ae" />
+  <public type="color" name="system_palette_key_color_primary_dark" id="0x010600af" />
+  <public type="color" name="system_palette_key_color_secondary_dark" id="0x010600b0" />
+  <public type="color" name="system_palette_key_color_tertiary_dark" id="0x010600b1" />
+  <public type="color" name="system_palette_key_color_neutral_dark" id="0x010600b2" />
+  <public type="color" name="system_palette_key_color_neutral_variant_dark" id="0x010600b3" />
+  <public type="color" name="system_primary_fixed" id="0x010600b4" />
+  <public type="color" name="system_primary_fixed_dim" id="0x010600b5" />
+  <public type="color" name="system_on_primary_fixed" id="0x010600b6" />
+  <public type="color" name="system_on_primary_fixed_variant" id="0x010600b7" />
+  <public type="color" name="system_secondary_fixed" id="0x010600b8" />
+  <public type="color" name="system_secondary_fixed_dim" id="0x010600b9" />
+  <public type="color" name="system_on_secondary_fixed" id="0x010600ba" />
+  <public type="color" name="system_on_secondary_fixed_variant" id="0x010600bb" />
+  <public type="color" name="system_tertiary_fixed" id="0x010600bc" />
+  <public type="color" name="system_tertiary_fixed_dim" id="0x010600bd" />
+  <public type="color" name="system_on_tertiary_fixed" id="0x010600be" />
+  <public type="color" name="system_on_tertiary_fixed_variant" id="0x010600bf" />
+  <public type="color" name="system_outline_variant_light" id="0x010600c0" />
+  <public type="color" name="system_outline_variant_dark" id="0x010600c1" />
+
+  <staging-public-group-final type="bool" first-id="0x01be0000">
+    <!-- @hide @SystemApi -->
+    <public name="config_safetyProtectionEnabled" />
+    <!-- @hide @SystemApi -->
+    <public name="config_enableDefaultNotes" />
+    <!-- @hide @SystemApi -->
+    <public name="config_enableDefaultNotesForWorkProfile" />
+  </staging-public-group-final>
+
+    <!-- @hide @SystemApi -->
+  <public type="bool" name="config_safetyProtectionEnabled" id="0x01110009" />
+    <!-- @hide @SystemApi -->
+  <public type="bool" name="config_enableDefaultNotes" id="0x0111000a" />
+    <!-- @hide @SystemApi -->
+  <public type="bool" name="config_enableDefaultNotesForWorkProfile" id="0x0111000b" />
+
 </resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 9cbf3b6..49a1940 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -102,231 +102,65 @@
 <resources>
 
   <!-- ===============================================================
-    Resources added in version U of the platform
+    Resources added in version NEXT of the platform
 
     NOTE: After this version of the platform is forked, changes cannot be made to the root
     branch's groups for that release. Only merge changes to the forked platform branch.
     =============================================================== -->
   <eat-comment/>
 
-  <staging-public-group type="attr" first-id="0x01ce0000">
-    <public name="handwritingBoundsOffsetLeft" />
-    <public name="handwritingBoundsOffsetTop" />
-    <public name="handwritingBoundsOffsetRight" />
-    <public name="handwritingBoundsOffsetBottom" />
-    <public name="accessibilityDataSensitive" />
-    <public name="enableTextStylingShortcuts" />
-    <public name="requiredDisplayCategory"/>
-    <public name="removed_maxConcurrentSessionsCount" />
-    <public name="visualQueryDetectionService" />
-    <public name="physicalKeyboardHintLanguageTag" />
-    <public name="physicalKeyboardHintLayoutType" />
-    <public name="allowSharedIsolatedProcess" />
-    <public name="keyboardLocale" />
-    <public name="keyboardLayoutType" />
-    <public name="allowUpdateOwnership" />
-    <public name="isCredential"/>
-    <public name="searchResultHighlightColor" />
-    <public name="focusedSearchResultHighlightColor" />
-    <public name="stylusHandwritingSettingsActivity" />
-    <public name="windowNoMoveAnimation" />
-    <public name="settingsSubtitle" />
-    <public name="capability" />
+  <staging-public-group type="attr" first-id="0x01bd0000">
   </staging-public-group>
 
-  <staging-public-group type="id" first-id="0x01cd0000">
-    <public name="bold" />
-    <public name="italic" />
-    <public name="underline" />
-    <public name="accessibilityActionScrollInDirection" />
+  <staging-public-group type="id" first-id="0x01bc0000">
   </staging-public-group>
 
-  <staging-public-group type="style" first-id="0x01cc0000">
+  <staging-public-group type="style" first-id="0x01bb0000">
   </staging-public-group>
 
-  <staging-public-group type="string" first-id="0x01cb0000">
-    <!-- @hide @SystemApi -->
-    <public name="config_systemWearHealthService" />
-    <!-- @hide @SystemApi -->
-    <public name="config_defaultNotes" />
-    <!-- @hide @SystemApi -->
-    <public name="config_systemFinancedDeviceController" />
-    <!-- @hide @SystemApi -->
-    <public name="config_systemCallStreaming" />
+  <staging-public-group type="string" first-id="0x01ba0000">
   </staging-public-group>
 
-  <staging-public-group type="dimen" first-id="0x01ca0000">
-    <!-- @hide @SystemApi -->
-    <public name="config_viewConfigurationHandwritingGestureLineMargin" />
+  <staging-public-group type="dimen" first-id="0x01b90000">
   </staging-public-group>
 
-  <staging-public-group type="color" first-id="0x01c90000">
-    <public name="system_primary_container_light" />
-    <public name="system_on_primary_container_light" />
-    <public name="system_primary_light" />
-    <public name="system_on_primary_light" />
-    <public name="system_secondary_container_light" />
-    <public name="system_on_secondary_container_light" />
-    <public name="system_secondary_light" />
-    <public name="system_on_secondary_light" />
-    <public name="system_tertiary_container_light" />
-    <public name="system_on_tertiary_container_light" />
-    <public name="system_tertiary_light" />
-    <public name="system_on_tertiary_light" />
-    <public name="system_background_light" />
-    <public name="system_on_background_light" />
-    <public name="system_surface_light" />
-    <public name="system_on_surface_light" />
-    <public name="system_surface_container_low_light" />
-    <public name="system_surface_container_lowest_light" />
-    <public name="system_surface_container_light" />
-    <public name="system_surface_container_high_light" />
-    <public name="system_surface_container_highest_light" />
-    <public name="system_surface_bright_light" />
-    <public name="system_surface_dim_light" />
-    <public name="system_surface_variant_light" />
-    <public name="system_on_surface_variant_light" />
-    <public name="system_outline_light" />
-    <public name="system_error_light" />
-    <public name="system_on_error_light" />
-    <public name="system_error_container_light" />
-    <public name="system_on_error_container_light" />
-    <public name="removed_system_primary_fixed_light" />
-    <public name="removed_system_primary_fixed_dim_light" />
-    <public name="removed_system_on_primary_fixed_light" />
-    <public name="removed_system_on_primary_fixed_variant_light" />
-    <public name="removed_system_secondary_fixed_light" />
-    <public name="removed_system_secondary_fixed_dim_light" />
-    <public name="removed_system_on_secondary_fixed_light" />
-    <public name="removed_system_on_secondary_fixed_variant_light" />
-    <public name="removed_system_tertiary_fixed_light" />
-    <public name="removed_system_tertiary_fixed_dim_light" />
-    <public name="removed_system_on_tertiary_fixed_light" />
-    <public name="removed_system_on_tertiary_fixed_variant_light" />
-    <public name="system_control_activated_light" />
-    <public name="system_control_normal_light" />
-    <public name="system_control_highlight_light" />
-    <public name="system_text_primary_inverse_light" />
-    <public name="system_text_secondary_and_tertiary_inverse_light" />
-    <public name="system_text_primary_inverse_disable_only_light" />
-    <public name="system_text_secondary_and_tertiary_inverse_disabled_light" />
-    <public name="system_text_hint_inverse_light" />
-    <public name="system_palette_key_color_primary_light" />
-    <public name="system_palette_key_color_secondary_light" />
-    <public name="system_palette_key_color_tertiary_light" />
-    <public name="system_palette_key_color_neutral_light" />
-    <public name="system_palette_key_color_neutral_variant_light" />
-    <public name="system_primary_container_dark"/>
-    <public name="system_on_primary_container_dark"/>
-    <public name="system_primary_dark"/>
-    <public name="system_on_primary_dark"/>
-    <public name="system_secondary_container_dark"/>
-    <public name="system_on_secondary_container_dark"/>
-    <public name="system_secondary_dark"/>
-    <public name="system_on_secondary_dark"/>
-    <public name="system_tertiary_container_dark"/>
-    <public name="system_on_tertiary_container_dark"/>
-    <public name="system_tertiary_dark"/>
-    <public name="system_on_tertiary_dark"/>
-    <public name="system_background_dark"/>
-    <public name="system_on_background_dark"/>
-    <public name="system_surface_dark"/>
-    <public name="system_on_surface_dark"/>
-    <public name="system_surface_container_low_dark"/>
-    <public name="system_surface_container_lowest_dark"/>
-    <public name="system_surface_container_dark"/>
-    <public name="system_surface_container_high_dark"/>
-    <public name="system_surface_container_highest_dark"/>
-    <public name="system_surface_bright_dark"/>
-    <public name="system_surface_dim_dark"/>
-    <public name="system_surface_variant_dark"/>
-    <public name="system_on_surface_variant_dark"/>
-    <public name="system_outline_dark"/>
-    <public name="system_error_dark"/>
-    <public name="system_on_error_dark"/>
-    <public name="system_error_container_dark"/>
-    <public name="system_on_error_container_dark"/>
-    <public name="removed_system_primary_fixed_dark"/>
-    <public name="removed_system_primary_fixed_dim_dark"/>
-    <public name="removed_system_on_primary_fixed_dark"/>
-    <public name="removed_system_on_primary_fixed_variant_dark"/>
-    <public name="removed_system_secondary_fixed_dark"/>
-    <public name="removed_system_secondary_fixed_dim_dark"/>
-    <public name="removed_system_on_secondary_fixed_dark"/>
-    <public name="removed_system_on_secondary_fixed_variant_dark"/>
-    <public name="removed_system_tertiary_fixed_dark"/>
-    <public name="removed_system_tertiary_fixed_dim_dark"/>
-    <public name="removed_system_on_tertiary_fixed_dark"/>
-    <public name="removed_system_on_tertiary_fixed_variant_dark"/>
-    <public name="system_control_activated_dark"/>
-    <public name="system_control_normal_dark"/>
-    <public name="system_control_highlight_dark"/>
-    <public name="system_text_primary_inverse_dark"/>
-    <public name="system_text_secondary_and_tertiary_inverse_dark"/>
-    <public name="system_text_primary_inverse_disable_only_dark"/>
-    <public name="system_text_secondary_and_tertiary_inverse_disabled_dark"/>
-    <public name="system_text_hint_inverse_dark"/>
-    <public name="system_palette_key_color_primary_dark"/>
-    <public name="system_palette_key_color_secondary_dark"/>
-    <public name="system_palette_key_color_tertiary_dark"/>
-    <public name="system_palette_key_color_neutral_dark"/>
-    <public name="system_palette_key_color_neutral_variant_dark"/>
-    <public name="system_primary_fixed" />
-    <public name="system_primary_fixed_dim" />
-    <public name="system_on_primary_fixed" />
-    <public name="system_on_primary_fixed_variant" />
-    <public name="system_secondary_fixed" />
-    <public name="system_secondary_fixed_dim" />
-    <public name="system_on_secondary_fixed" />
-    <public name="system_on_secondary_fixed_variant" />
-    <public name="system_tertiary_fixed" />
-    <public name="system_tertiary_fixed_dim" />
-    <public name="system_on_tertiary_fixed" />
-    <public name="system_on_tertiary_fixed_variant" />
-    <public name="system_outline_variant_light" />
-    <public name="system_outline_variant_dark" />
+  <staging-public-group type="color" first-id="0x01b80000">
   </staging-public-group>
 
-  <staging-public-group type="array" first-id="0x01c80000">
+  <staging-public-group type="array" first-id="0x01b70000">
   </staging-public-group>
 
-  <staging-public-group type="drawable" first-id="0x01c70000">
+  <staging-public-group type="drawable" first-id="0x01b60000">
   </staging-public-group>
 
-  <staging-public-group type="layout" first-id="0x01c60000">
+  <staging-public-group type="layout" first-id="0x01b50000">
   </staging-public-group>
 
-  <staging-public-group type="anim" first-id="0x01c50000">
+  <staging-public-group type="anim" first-id="0x01b40000">
   </staging-public-group>
 
-  <staging-public-group type="animator" first-id="0x01c40000">
+  <staging-public-group type="animator" first-id="0x01b30000">
   </staging-public-group>
 
-  <staging-public-group type="interpolator" first-id="0x01c30000">
+  <staging-public-group type="interpolator" first-id="0x01b20000">
   </staging-public-group>
 
-  <staging-public-group type="mipmap" first-id="0x01c20000">
+  <staging-public-group type="mipmap" first-id="0x01b10000">
   </staging-public-group>
 
-  <staging-public-group type="integer" first-id="0x01c10000">
+  <staging-public-group type="integer" first-id="0x01b00000">
   </staging-public-group>
 
-  <staging-public-group type="transition" first-id="0x01c00000">
+  <staging-public-group type="transition" first-id="0x01af0000">
   </staging-public-group>
 
-  <staging-public-group type="raw" first-id="0x01bf0000">
+  <staging-public-group type="raw" first-id="0x01ae0000">
   </staging-public-group>
 
-  <staging-public-group type="bool" first-id="0x01be0000">
-    <!-- @hide @SystemApi -->
-    <public name="config_safetyProtectionEnabled" />
-    <!-- @hide @SystemApi -->
-    <public name="config_enableDefaultNotes" />
-    <!-- @hide @SystemApi -->
-    <public name="config_enableDefaultNotesForWorkProfile" />
+  <staging-public-group type="bool" first-id="0x01ad0000">
   </staging-public-group>
 
-  <staging-public-group type="fraction" first-id="0x01bd0000">
+  <staging-public-group type="fraction" first-id="0x01ac0000">
   </staging-public-group>
 
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 3ee8af2..947dc2d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1347,16 +1347,6 @@
     <!-- Description of the background body sensors permission, listed so the user can decide whether to allow the application to access data from body sensors in the background. [CHAR LIMIT=NONE] -->
     <string name="permdesc_bodySensors_background" product="default">Allows the app to access body sensor data, such as heart rate, temperature, and blood oxygen percentage, while the app is in the background.</string>
 
-    <!-- Title of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access body sensor wrist temperature data. [CHAR LIMIT=NONE] -->
-    <string name="permlab_bodySensorsWristTemperature">Access body sensor wrist temperature data while the app is in use.</string>
-    <!-- Description of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] -->
-    <string name="permdesc_bodySensorsWristTemperature" product="default">Allows the app to access body sensor wrist temperature data, while the app is in use.</string>
-
-    <!-- Title of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access body sensor wrist temperature data. [CHAR LIMIT=NONE] -->
-    <string name="permlab_bodySensors_wristTemperature_background">Access body sensor wrist temperature data while the app is in the background.</string>
-    <!-- Description of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] -->
-    <string name="permdesc_bodySensors_wristTemperature_background" product="default">Allows the app to access body sensor wrist temperature data, while the app is in the background.</string>
-
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_readCalendar">Read calendar events and details</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -5369,15 +5359,11 @@
     <string name="app_suspended_unsuspend_message">Unpause app</string>
 
     <!-- Title of a dialog. This text is confirming that the user wants to turn on access to their work apps, which the user had previously paused. "Work" is an adjective. [CHAR LIMIT=30] -->
-    <string name="work_mode_off_title">Turn on work apps?</string>
-    <!-- Text in a dialog. This text is confirming that the user wants to turn on access to their work apps and notifications, which the user had previously paused. "Work" is an adjective. [CHAR LIMIT=NONE] -->
-    <string name="work_mode_off_message">Get access to your work apps and notifications</string>
-    <!-- Title for button to turn on work profile. [CHAR LIMIT=NONE] -->
-    <string name="work_mode_turn_on">Turn on</string>
+    <string name="work_mode_off_title">Unpause work apps?</string>
+    <!-- Title for button to unpause on work profile. [CHAR LIMIT=NONE] -->
+    <string name="work_mode_turn_on">Unpause</string>
     <!-- Title for button to launch the personal safety app to make an emergency call    -->
     <string name="work_mode_emergency_call_button">Emergency</string>
-    <!-- Text shown in a dialog when the user tries to launch a disabled work profile app when work apps are paused-->
-    <string name="work_mode_dialer_off_message">Get access to your work apps and calls</string>
 
     <!-- Title of the dialog that is shown when the user tries to launch a blocked application [CHAR LIMIT=50] -->
     <string name="app_blocked_title">App is not available</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 315100c..3fa76d6 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1283,6 +1283,8 @@
   <java-symbol type="dimen" name="lock_pattern_fade_away_gradient_width" />
   <java-symbol type="integer" name="lock_pattern_line_fade_out_duration" />
   <java-symbol type="integer" name="lock_pattern_line_fade_out_delay" />
+  <java-symbol type="integer" name="lock_pattern_fade_pattern_delay" />
+  <java-symbol type="integer" name="lock_pattern_fade_pattern_duration" />
   <java-symbol type="drawable" name="clock_dial" />
   <java-symbol type="drawable" name="clock_hand_hour" />
   <java-symbol type="drawable" name="clock_hand_minute" />
@@ -3139,7 +3141,6 @@
 
   <!--  Work profile unlaunchable app alert dialog-->
   <java-symbol type="style" name="AlertDialogWithEmergencyButton"/>
-  <java-symbol type="string" name="work_mode_dialer_off_message" />
   <java-symbol type="string" name="work_mode_emergency_call_button" />
   <java-symbol type="string" name="work_mode_off_title" />
   <java-symbol type="string" name="work_mode_off_message" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 6b041dd..147d74e 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -278,6 +278,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -371,6 +372,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -463,6 +465,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -557,6 +560,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -650,6 +654,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -751,6 +756,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -843,6 +849,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -934,6 +941,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -1026,6 +1034,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -1134,6 +1143,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -1227,6 +1237,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -1318,6 +1329,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -1411,6 +1423,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -1503,6 +1516,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -1595,6 +1609,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -1687,6 +1702,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -1779,6 +1795,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -1871,6 +1888,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -1968,6 +1986,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -2058,6 +2077,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -2286,6 +2306,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -2378,6 +2399,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -2469,6 +2491,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -2561,6 +2584,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -2655,6 +2679,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -2748,6 +2773,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -2847,6 +2873,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -2942,6 +2969,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -3036,6 +3064,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -3131,6 +3160,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -3207,6 +3237,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -3283,6 +3314,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -3378,6 +3410,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -3474,6 +3507,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -3568,6 +3602,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -3661,6 +3696,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -3753,6 +3789,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -3845,6 +3882,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -3935,6 +3973,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -4281,6 +4320,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -4475,6 +4515,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -4569,6 +4610,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -4689,6 +4731,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -4740,6 +4783,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -4794,6 +4838,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
@@ -4844,6 +4889,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
         <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
         <item name="materialColorOnSurface">@color/system_on_surface_light</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
@@ -4905,6 +4951,7 @@
         <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
         <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
         <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
         <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
         <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
         <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index 980211f..c6bb07b 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -25,6 +25,7 @@
 import android.app.Activity;
 import android.compat.testing.PlatformCompatChangeRule;
 import android.os.Bundle;
+import android.platform.test.annotations.IwTest;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.util.PollingCheck;
@@ -84,6 +85,7 @@
         }
     }
 
+    @IwTest(focusArea = "accessibility")
     @Test
     public void testFontsScaleNonLinearly() {
         final ActivityScenario<TestActivity> scenario = rule.getScenario();
@@ -114,6 +116,7 @@
         )));
     }
 
+    @IwTest(focusArea = "accessibility")
     @Test
     public void testOnConfigurationChanged_doesNotCrash() {
         final ActivityScenario<TestActivity> scenario = rule.getScenario();
@@ -127,6 +130,7 @@
         });
     }
 
+    @IwTest(focusArea = "accessibility")
     @Test
     public void testUpdateConfiguration_doesNotCrash() {
         final ActivityScenario<TestActivity> scenario = rule.getScenario();
diff --git a/core/tests/coretests/src/android/content/res/TEST_MAPPING b/core/tests/coretests/src/android/content/res/TEST_MAPPING
index 4ea6e40..ab14950 100644
--- a/core/tests/coretests/src/android/content/res/TEST_MAPPING
+++ b/core/tests/coretests/src/android/content/res/TEST_MAPPING
@@ -39,5 +39,18 @@
         }
       ]
     }
+  ],
+  "ironwood-postsubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options":[
+        {
+            "include-annotation": "android.platform.test.annotations.IwTest"
+        },
+        {
+            "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
   ]
 }
diff --git a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
new file mode 100644
index 0000000..66f3bca
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.junit.MockitoRule;
+
+import java.util.concurrent.Executor;
+
+
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class BiometricPromptTest {
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private IAuthService mService;
+    private BiometricPrompt mBiometricPrompt;
+
+    private CancellationSignal mCancellationSignal;
+
+    private final TestLooper mLooper = new TestLooper();
+    private final Handler mHandler = new Handler(mLooper.getLooper());
+    private final Executor mExecutor = mHandler::post;
+
+    @Before
+    public void setUp() throws RemoteException {
+        mBiometricPrompt = new BiometricPrompt.Builder(mContext)
+                .setUseDefaultSubtitle()
+                .setUseDefaultTitle()
+                .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG
+                        | BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+                .setService(mService)
+                .build();
+
+        mCancellationSignal = new CancellationSignal();
+        when(mService.authenticate(any(), anyLong(), anyInt(), any(), anyString(), any()))
+                .thenReturn(0L);
+        when(mContext.getPackageName()).thenReturn("BiometricPromptTest");
+    }
+
+    @Test
+    public void testCancellationAfterAuthenticationFailed() throws RemoteException {
+        ArgumentCaptor<IBiometricServiceReceiver> biometricServiceReceiverCaptor =
+                ArgumentCaptor.forClass(IBiometricServiceReceiver.class);
+        BiometricPrompt.AuthenticationCallback callback =
+                new BiometricPrompt.AuthenticationCallback() {
+            @Override
+            public void onAuthenticationError(int errorCode, CharSequence errString) {
+                super.onAuthenticationError(errorCode, errString);
+            }};
+        mBiometricPrompt.authenticate(mCancellationSignal, mExecutor, callback);
+        mLooper.dispatchAll();
+
+        verify(mService).authenticate(any(), anyLong(), anyInt(),
+                biometricServiceReceiverCaptor.capture(), anyString(), any());
+
+        biometricServiceReceiverCaptor.getValue().onAuthenticationFailed();
+        mLooper.dispatchAll();
+        mCancellationSignal.cancel();
+
+        verify(mService).cancelAuthentication(any(), anyString(), anyLong());
+    }
+}
diff --git a/core/tests/coretests/src/android/hardware/biometrics/OWNERS b/core/tests/coretests/src/android/hardware/biometrics/OWNERS
new file mode 100644
index 0000000..6a2192a
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/biometrics/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/biometrics/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
index 4716312..36c2a62 100644
--- a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
@@ -23,16 +23,25 @@
 import static com.android.internal.util.DumpUtils.isPlatformNonCriticalPackage;
 import static com.android.internal.util.DumpUtils.isPlatformPackage;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.content.ComponentName;
+import android.util.SparseArray;
 
 import junit.framework.TestCase;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
 /**
  * Run with:
  atest FrameworksCoreTests:DumpUtilsTest
  */
 public class DumpUtilsTest extends TestCase {
 
+    private final StringWriter mStringWriter = new StringWriter();
+    private final PrintWriter mPrintWriter = new PrintWriter(mStringWriter);
+
     private static ComponentName cn(String componentName) {
         if (componentName == null) {
             return null;
@@ -168,4 +177,144 @@
                 Integer.toHexString(System.identityHashCode(component))).test(
                         wcn("com.google/.abc")));
     }
+
+    public void testDumpSparseArray_empty() {
+        SparseArray<String> array = new SparseArray<>();
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ "...", array, "whatever");
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("empty array dump").that(output).isEqualTo("...No whatevers\n");
+    }
+
+    public void testDumpSparseArray_oneElement() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, "uno");
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number");
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".1 number(s):\n"
+                + "..0: 1->uno\n");
+    }
+
+    public void testDumpSparseArray_oneNullElement() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, null);
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "NULL");
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".1 NULL(s):\n"
+                + "..0: 1->(null)\n");
+    }
+
+    public void testDumpSparseArray_multipleElements() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, "uno");
+        array.put(2, "duo");
+        array.put(42, null);
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number");
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".3 number(s):\n"
+                + "..0: 1->uno\n"
+                + "..1: 2->duo\n"
+                + "..2: 42->(null)\n");
+    }
+
+    public void testDumpSparseArray_keyDumperOnly() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, "uno");
+        array.put(2, "duo");
+        array.put(42, null);
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number",
+                (i, k) -> {
+                    mPrintWriter.printf("_%d=%d_", i, k);
+                }, /* valueDumper= */ null);
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".3 number(s):\n"
+                + "_0=1_uno\n"
+                + "_1=2_duo\n"
+                + "_2=42_(null)\n");
+    }
+
+    public void testDumpSparseArray_valueDumperOnly() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, "uno");
+        array.put(2, "duo");
+        array.put(42, null);
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number",
+                /* keyDumper= */ null,
+                s -> {
+                    mPrintWriter.print(s.toUpperCase());
+                });
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".3 number(s):\n"
+                + "..0: 1->UNO\n"
+                + "..1: 2->DUO\n"
+                + "..2: 42->(null)\n");
+    }
+
+    public void testDumpSparseArray_keyAndValueDumpers() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, "uno");
+        array.put(2, "duo");
+        array.put(42, null);
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number",
+                (i, k) -> {
+                    mPrintWriter.printf("_%d=%d_", i, k);
+                },
+                s -> {
+                    mPrintWriter.print(s.toUpperCase());
+                });
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".3 number(s):\n"
+                + "_0=1_UNO\n"
+                + "_1=2_DUO\n"
+                + "_2=42_(null)\n");
+    }
+
+    public void testDumpSparseArrayValues() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, "uno");
+        array.put(2, "duo");
+        array.put(42, null);
+
+        DumpUtils.dumpSparseArrayValues(mPrintWriter, /* prefix= */ ".", array, "number");
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".3 numbers:\n"
+                + "..uno\n"
+                + "..duo\n"
+                + "..(null)\n");
+    }
+
+    private String flushPrintWriter() {
+        mPrintWriter.flush();
+
+        return mStringWriter.toString();
+    }
 }
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 2afd54b..0eb4caa 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -475,6 +475,18 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/RecentTasks.java"
     },
+    "-1643780158": {
+      "message": "Saving original orientation before camera compat, last orientation is %d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+    },
+    "-1639406696": {
+      "message": "NOSENSOR override detected",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotationReversionController.java"
+    },
     "-1638958146": {
       "message": "Removing activity %s from task=%s adding to task=%s Callers=%s",
       "level": "INFO",
@@ -751,6 +763,12 @@
       "group": "WM_DEBUG_IME",
       "at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java"
     },
+    "-1397175017": {
+      "message": "Other orientation overrides are in place: not reverting",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotationReversionController.java"
+    },
     "-1394745488": {
       "message": "ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s",
       "level": "INFO",
@@ -883,6 +901,12 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
     },
+    "-1258739769": {
+      "message": "onTransactionReady, opening: %s, closing: %s, animating: %s, match: %b",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_BACK_PREVIEW",
+      "at": "com\/android\/server\/wm\/BackNavigationController.java"
+    },
     "-1257821162": {
       "message": "OUT SURFACE %s: copied",
       "level": "INFO",
@@ -1297,6 +1321,12 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-874087484": {
+      "message": "SyncGroup %d: Set ready %b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_SYNC_ENGINE",
+      "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
+    },
     "-869242375": {
       "message": "Content Recording: Unable to start recording due to invalid region for display %d",
       "level": "VERBOSE",
@@ -1699,6 +1729,12 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
+    "-529187878": {
+      "message": "Reverting orientation after camera compat force rotation",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+    },
     "-521613870": {
       "message": "App died during pause, not stopping: %s",
       "level": "VERBOSE",
@@ -2371,6 +2407,12 @@
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "138097009": {
+      "message": "NOSENSOR override is absent: reverting",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotationReversionController.java"
+    },
     "140319294": {
       "message": "IME target changed within ActivityRecord",
       "level": "DEBUG",
@@ -4003,12 +4045,6 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
-    "1689989893": {
-      "message": "SyncGroup %d: Set ready",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_SYNC_ENGINE",
-      "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
-    },
     "1699269281": {
       "message": "Don't organize or trigger events for untrusted displayId=%d",
       "level": "WARN",
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 8dd23b7..2307d60 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -401,8 +401,9 @@
     /**
      * This is called by methods that want to throw an exception if the bitmap
      * has already been recycled.
+     * @hide
      */
-    private void checkRecycled(String errorMessage) {
+    void checkRecycled(String errorMessage) {
         if (mRecycled) {
             throw new IllegalStateException(errorMessage);
         }
@@ -1921,6 +1922,7 @@
      */
     public void setGainmap(@Nullable Gainmap gainmap) {
         checkRecycled("Bitmap is recycled");
+        mGainmap = null;
         nativeSetGainmap(mNativePtr, gainmap == null ? 0 : gainmap.mNativePtr);
     }
 
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 701e20c..1da8e18 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -482,7 +482,9 @@
             if (opts == null || opts.inBitmap == null) {
                 return 0;
             }
-
+            // Clear out the gainmap since we don't attempt to reuse it and don't want to
+            // accidentally keep it on the re-used bitmap
+            opts.inBitmap.setGainmap(null);
             return opts.inBitmap.getNativeInstance();
         }
 
diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java
index 2f6dd46..5c06577 100644
--- a/graphics/java/android/graphics/BitmapShader.java
+++ b/graphics/java/android/graphics/BitmapShader.java
@@ -120,6 +120,7 @@
         if (bitmap == null) {
             throw new IllegalArgumentException("Bitmap must be non-null");
         }
+        bitmap.checkRecycled("Cannot create BitmapShader for recycled bitmap");
         mBitmap = bitmap;
         mTileX = tileX;
         mTileY = tileY;
@@ -188,6 +189,8 @@
     /** @hide */
     @Override
     protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) {
+        mBitmap.checkRecycled("BitmapShader's bitmap has been recycled");
+
         boolean enableLinearFilter = mFilterMode == FILTER_MODE_LINEAR;
         if (mFilterMode == FILTER_MODE_DEFAULT) {
             mFilterFromPaint = filterFromPaint;
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 0b29973..56c3068 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -2083,32 +2083,29 @@
             }
 
             sIsP010SupportedForAV1Initialized = true;
-
-            if (hasHardwareDecoder("video/av01")) {
-                sIsP010SupportedForAV1 = true;
-                return true;
-            }
-
-            sIsP010SupportedForAV1 = Build.VERSION.DEVICE_INITIAL_SDK_INT
-                    >= Build.VERSION_CODES.S;
-            return sIsP010SupportedForAV1;
+            return sIsP010SupportedForAV1 = isP010SupportedforMime("video/av01");
         }
     }
 
     /**
-     * Checks if the device has hardware decoder for the target mime type.
+     * Checks if the device supports decoding 10-bit for the given mime type.
      */
-    private static boolean hasHardwareDecoder(String mime) {
-        final MediaCodecList sMCL = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        for (MediaCodecInfo info : sMCL.getCodecInfos()) {
-            if (info.isEncoder() == false && info.isHardwareAccelerated()) {
-                try {
-                     if (info.getCapabilitiesForType(mime) != null) {
-                         return true;
-                     }
-                } catch (IllegalArgumentException e) {
-                     // mime is not supported
-                     return false;
+    private static boolean isP010SupportedforMime(String mime) {
+        MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        for (MediaCodecInfo mediaCodecInfo : codecList.getCodecInfos()) {
+            if (mediaCodecInfo.isEncoder()) {
+                continue;
+            }
+            for (String mediaType : mediaCodecInfo.getSupportedTypes()) {
+                if (mediaType.equalsIgnoreCase(mime)) {
+                    MediaCodecInfo.CodecCapabilities codecCapabilities =
+                        mediaCodecInfo.getCapabilitiesForType(mediaType);
+                    for (int i = 0; i < codecCapabilities.colorFormats.length; ++i) {
+                        if (codecCapabilities.colorFormats[i]
+                            == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010) {
+                            return true;
+                        }
+                    }
                 }
             }
         }
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
index d785c3c..f26b50e 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
@@ -21,10 +21,7 @@
 import android.content.Context;
 import android.content.pm.FeatureInfo;
 import android.content.pm.PackageManager;
-import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.security.GenerateRkpKey;
-import android.security.keymaster.KeymasterDefs;
 
 class CredstoreIdentityCredentialStore extends IdentityCredentialStore {
 
@@ -125,18 +122,7 @@
             @NonNull String docType) throws AlreadyPersonalizedException,
             DocTypeNotSupportedException {
         try {
-            IWritableCredential wc;
-            wc = mStore.createCredential(credentialName, docType);
-            try {
-                GenerateRkpKey keyGen = new GenerateRkpKey(mContext);
-                // We don't know what the security level is for the backing keymint, so go ahead and
-                // poke the provisioner for both TEE and SB.
-                keyGen.notifyKeyGenerated(KeymasterDefs.KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT);
-                keyGen.notifyKeyGenerated(KeymasterDefs.KM_SECURITY_LEVEL_STRONGBOX);
-            } catch (RemoteException e) {
-                // Not really an error state. Does not apply at all if RKP is unsupported or
-                // disabled on a given device.
-            }
+            IWritableCredential wc = mStore.createCredential(credentialName, docType);
             return new CredstoreWritableIdentityCredential(mContext, credentialName, docType, wc);
         } catch (android.os.RemoteException e) {
             throw new RuntimeException("Unexpected RemoteException ", e);
diff --git a/keystore/java/android/security/GenerateRkpKey.java b/keystore/java/android/security/GenerateRkpKey.java
deleted file mode 100644
index 6981332..0000000
--- a/keystore/java/android/security/GenerateRkpKey.java
+++ /dev/null
@@ -1,159 +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 android.security;
-
-import android.annotation.CheckResult;
-import android.annotation.IntDef;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-/**
- * GenerateKey is a helper class to handle interactions between Keystore and the RemoteProvisioner
- * app. There are two cases where Keystore should use this class.
- *
- * (1) : An app generates a new attested key pair, so Keystore calls notifyKeyGenerated to let the
- *       RemoteProvisioner app check if the state of the attestation key pool is getting low enough
- *       to warrant provisioning more attestation certificates early.
- *
- * (2) : An app attempts to generate a new key pair, but the keystore service discovers it is out of
- *       attestation key pairs and cannot provide one for the given application. Keystore can then
- *       make a blocking call on notifyEmpty to allow the RemoteProvisioner app to get another
- *       attestation certificate chain provisioned.
- *
- * In most cases, the proper usage of (1) should preclude the need for (2).
- *
- * @hide
- */
-public class GenerateRkpKey {
-    private static final String TAG = "GenerateRkpKey";
-
-    private static final int NOTIFY_EMPTY = 0;
-    private static final int NOTIFY_KEY_GENERATED = 1;
-    private static final int TIMEOUT_MS = 1000;
-
-    private IGenerateRkpKeyService mBinder;
-    private Context mContext;
-    private CountDownLatch mCountDownLatch;
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = true, value = {
-            IGenerateRkpKeyService.Status.OK,
-            IGenerateRkpKeyService.Status.NO_NETWORK_CONNECTIVITY,
-            IGenerateRkpKeyService.Status.NETWORK_COMMUNICATION_ERROR,
-            IGenerateRkpKeyService.Status.DEVICE_NOT_REGISTERED,
-            IGenerateRkpKeyService.Status.HTTP_CLIENT_ERROR,
-            IGenerateRkpKeyService.Status.HTTP_SERVER_ERROR,
-            IGenerateRkpKeyService.Status.HTTP_UNKNOWN_ERROR,
-            IGenerateRkpKeyService.Status.INTERNAL_ERROR,
-    })
-    public @interface Status {
-    }
-
-    private ServiceConnection mConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            mBinder = IGenerateRkpKeyService.Stub.asInterface(service);
-            mCountDownLatch.countDown();
-        }
-
-        @Override public void onBindingDied(ComponentName className) {
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName className) {
-            mBinder = null;
-        }
-    };
-
-    /**
-     * Constructor which takes a Context object.
-     */
-    public GenerateRkpKey(Context context) {
-        mContext = context;
-    }
-
-    @Status
-    private int bindAndSendCommand(int command, int securityLevel) throws RemoteException {
-        Intent intent = new Intent(IGenerateRkpKeyService.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        int returnCode = IGenerateRkpKeyService.Status.OK;
-        if (comp == null) {
-            // On a system that does not use RKP, the RemoteProvisioner app won't be installed.
-            return returnCode;
-        }
-        intent.setComponent(comp);
-        mCountDownLatch = new CountDownLatch(1);
-        Executor executor = Executors.newCachedThreadPool();
-        if (!mContext.bindService(intent, Context.BIND_AUTO_CREATE, executor, mConnection)) {
-            throw new RemoteException("Failed to bind to GenerateRkpKeyService");
-        }
-        try {
-            mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            Log.e(TAG, "Interrupted: ", e);
-        }
-        if (mBinder != null) {
-            switch (command) {
-                case NOTIFY_EMPTY:
-                    returnCode = mBinder.generateKey(securityLevel);
-                    break;
-                case NOTIFY_KEY_GENERATED:
-                    mBinder.notifyKeyGenerated(securityLevel);
-                    break;
-                default:
-                    Log.e(TAG, "Invalid case for command");
-            }
-        } else {
-            Log.e(TAG, "Binder object is null; failed to bind to GenerateRkpKeyService.");
-            returnCode = IGenerateRkpKeyService.Status.INTERNAL_ERROR;
-        }
-        mContext.unbindService(mConnection);
-        return returnCode;
-    }
-
-    /**
-     * Fulfills the use case of (2) described in the class documentation. Blocks until the
-     * RemoteProvisioner application can get new attestation keys signed by the server.
-     * @return the status of the key generation
-     */
-    @CheckResult
-    @Status
-    public int notifyEmpty(int securityLevel) throws RemoteException {
-        return bindAndSendCommand(NOTIFY_EMPTY, securityLevel);
-    }
-
-    /**
-     * Fulfills the use case of (1) described in the class documentation. Non blocking call.
-     */
-    public void notifyKeyGenerated(int securityLevel) throws RemoteException {
-        bindAndSendCommand(NOTIFY_KEY_GENERATED, securityLevel);
-    }
-}
diff --git a/keystore/java/android/security/IGenerateRkpKeyService.aidl b/keystore/java/android/security/IGenerateRkpKeyService.aidl
deleted file mode 100644
index eeaeb27..0000000
--- a/keystore/java/android/security/IGenerateRkpKeyService.aidl
+++ /dev/null
@@ -1,60 +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 android.security;
-
-/**
- * Interface to allow the framework to notify the RemoteProvisioner app when keys are empty. This
- * will be used if Keystore replies with an error code NO_KEYS_AVAILABLE in response to an
- * attestation request. The framework can then synchronously call generateKey() to get more
- * attestation keys generated and signed. Upon return, the caller can be certain an attestation key
- * is available.
- *
- * @hide
- */
-interface IGenerateRkpKeyService {
-    @JavaDerive(toString=true)
-    @Backing(type="int")
-    enum Status {
-        /** No error(s) occurred */
-        OK = 0,
-        /** Unable to provision keys due to a lack of internet connectivity. */
-        NO_NETWORK_CONNECTIVITY = 1,
-        /** An error occurred while communicating with the RKP server. */
-        NETWORK_COMMUNICATION_ERROR = 2,
-        /** The given device was not registered with the RKP backend. */
-        DEVICE_NOT_REGISTERED = 4,
-        /** The RKP server returned an HTTP client error, indicating a misbehaving client. */
-        HTTP_CLIENT_ERROR = 5,
-        /** The RKP server returned an HTTP server error, indicating something went wrong on the server. */
-        HTTP_SERVER_ERROR = 6,
-        /** The RKP server returned an HTTP status that is unknown. This should never happen. */
-        HTTP_UNKNOWN_ERROR = 7,
-        /** An unexpected internal error occurred. This should never happen. */
-        INTERNAL_ERROR = 8,
-    }
-
-    /**
-     * Ping the provisioner service to let it know an app generated a key. This may or may not have
-     * consumed a remotely provisioned attestation key, so the RemoteProvisioner app should check.
-     */
-    oneway void notifyKeyGenerated(in int securityLevel);
-
-    /**
-     * Ping the provisioner service to indicate there are no remaining attestation keys left.
-     */
-    Status generateKey(in int securityLevel);
-}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index c3b0f9b..474b7ea 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -20,7 +20,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityThread;
 import android.content.Context;
 import android.hardware.security.keymint.EcCurve;
 import android.hardware.security.keymint.KeyParameter;
@@ -28,9 +27,6 @@
 import android.hardware.security.keymint.SecurityLevel;
 import android.hardware.security.keymint.Tag;
 import android.os.Build;
-import android.os.RemoteException;
-import android.security.GenerateRkpKey;
-import android.security.IGenerateRkpKeyService;
 import android.security.KeyPairGeneratorSpec;
 import android.security.KeyStore2;
 import android.security.KeyStoreException;
@@ -621,45 +617,6 @@
 
     @Override
     public KeyPair generateKeyPair() {
-        GenerateKeyPairHelperResult result = new GenerateKeyPairHelperResult(0, null);
-        for (int i = 0; i < 2; i++) {
-            /**
-             * NOTE: There is no need to delay between re-tries because the call to
-             * GenerateRkpKey.notifyEmpty() will delay for a while before returning.
-             */
-            result = generateKeyPairHelper();
-            if (result.rkpStatus == KeyStoreException.RKP_SUCCESS && result.keyPair != null) {
-                return result.keyPair;
-            }
-        }
-
-        // RKP failure
-        if (result.rkpStatus != KeyStoreException.RKP_SUCCESS) {
-            KeyStoreException ksException = new KeyStoreException(ResponseCode.OUT_OF_KEYS,
-                    "Could not get RKP keys", result.rkpStatus);
-            throw new ProviderException("Failed to provision new attestation keys.", ksException);
-        }
-
-        return result.keyPair;
-    }
-
-    private static class GenerateKeyPairHelperResult {
-        // Zero indicates success, non-zero indicates failure. Values should be
-        // {@link android.security.KeyStoreException#RKP_TEMPORARILY_UNAVAILABLE},
-        // {@link android.security.KeyStoreException#RKP_SERVER_REFUSED_ISSUANCE},
-        // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_CONNECTIVITY}
-        // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_SOFTWARE_REBOOT}
-        public final int rkpStatus;
-        @Nullable
-        public final KeyPair keyPair;
-
-        private GenerateKeyPairHelperResult(int rkpStatus, KeyPair keyPair) {
-            this.rkpStatus = rkpStatus;
-            this.keyPair = keyPair;
-        }
-    }
-
-    private GenerateKeyPairHelperResult generateKeyPairHelper() {
         if (mKeyStore == null || mSpec == null) {
             throw new IllegalStateException("Not initialized");
         }
@@ -697,26 +654,12 @@
             AndroidKeyStorePublicKey publicKey =
                     AndroidKeyStoreProvider.makeAndroidKeyStorePublicKeyFromKeyEntryResponse(
                             descriptor, metadata, iSecurityLevel, mKeymasterAlgorithm);
-            GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread
-                    .currentApplication());
-            try {
-                if (mSpec.getAttestationChallenge() != null) {
-                    keyGen.notifyKeyGenerated(securityLevel);
-                }
-            } catch (RemoteException e) {
-                // This is not really an error state, and necessarily does not apply to non RKP
-                // systems or hybrid systems where RKP is not currently turned on.
-                Log.d(TAG, "Couldn't connect to the RemoteProvisioner backend.", e);
-            }
             success = true;
-            KeyPair kp = new KeyPair(publicKey, publicKey.getPrivateKey());
-            return new GenerateKeyPairHelperResult(0, kp);
+            return new KeyPair(publicKey, publicKey.getPrivateKey());
         } catch (KeyStoreException e) {
             switch (e.getErrorCode()) {
                 case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE:
                     throw new StrongBoxUnavailableException("Failed to generated key pair.", e);
-                case ResponseCode.OUT_OF_KEYS:
-                    return checkIfRetryableOrThrow(e, securityLevel);
                 default:
                     ProviderException p = new ProviderException("Failed to generate key pair.", e);
                     if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) {
@@ -742,55 +685,6 @@
         }
     }
 
-    // In case keystore reports OUT_OF_KEYS, call this handler in an attempt to remotely provision
-    // some keys.
-    GenerateKeyPairHelperResult checkIfRetryableOrThrow(KeyStoreException e, int securityLevel) {
-        GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread
-                .currentApplication());
-        KeyStoreException ksException;
-        try {
-            final int keyGenStatus = keyGen.notifyEmpty(securityLevel);
-            // Default stance: temporary error. This is a hint to the caller to try again with
-            // exponential back-off.
-            int rkpStatus;
-            switch (keyGenStatus) {
-                case IGenerateRkpKeyService.Status.NO_NETWORK_CONNECTIVITY:
-                    rkpStatus = KeyStoreException.RKP_FETCHING_PENDING_CONNECTIVITY;
-                    break;
-                case IGenerateRkpKeyService.Status.DEVICE_NOT_REGISTERED:
-                    rkpStatus = KeyStoreException.RKP_SERVER_REFUSED_ISSUANCE;
-                    break;
-                case IGenerateRkpKeyService.Status.OK:
-                    // Explicitly return not-OK here so we retry in generateKeyPair. All other cases
-                    // should throw because a retry doesn't make sense if we didn't actually
-                    // provision fresh keys.
-                    return new GenerateKeyPairHelperResult(
-                            KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE, null);
-                case IGenerateRkpKeyService.Status.NETWORK_COMMUNICATION_ERROR:
-                case IGenerateRkpKeyService.Status.HTTP_CLIENT_ERROR:
-                case IGenerateRkpKeyService.Status.HTTP_SERVER_ERROR:
-                case IGenerateRkpKeyService.Status.HTTP_UNKNOWN_ERROR:
-                case IGenerateRkpKeyService.Status.INTERNAL_ERROR:
-                default:
-                    // These errors really should never happen. The best we can do is assume they
-                    // are transient and hint to the caller to retry with back-off.
-                    rkpStatus = KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE;
-                    break;
-            }
-            ksException = new KeyStoreException(
-                    ResponseCode.OUT_OF_KEYS,
-                    "Out of RKP keys due to IGenerateRkpKeyService status: " + keyGenStatus,
-                    rkpStatus);
-        } catch (RemoteException f) {
-            ksException = new KeyStoreException(
-                    ResponseCode.OUT_OF_KEYS,
-                    "Remote exception: " + f.getMessage(),
-                    KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE);
-        }
-        ksException.initCause(e);
-        throw new ProviderException("Failed to provision new attestation keys.", ksException);
-    }
-
     private void addAttestationParameters(@NonNull List<KeyParameter> params)
             throws ProviderException, IllegalArgumentException, DeviceIdAttestationException {
         byte[] challenge = mSpec.getAttestationChallenge();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 8f364b4..026ea069 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -47,6 +47,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.InstanceId;
+import com.android.launcher3.icons.BubbleBadgeIconFactory;
+import com.android.launcher3.icons.BubbleIconFactory;
 import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
 import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
 import com.android.wm.shell.common.bubbles.BubbleInfo;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
deleted file mode 100644
index 56b13b8..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.bubbles;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.Rect;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.Drawable;
-
-import com.android.launcher3.icons.BaseIconFactory;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.wm.shell.R;
-
-/**
- * Factory for creating app badge icons that are shown on bubbles.
- */
-public class BubbleBadgeIconFactory extends BaseIconFactory {
-
-    public BubbleBadgeIconFactory(Context context) {
-        super(context, context.getResources().getConfiguration().densityDpi,
-                context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size));
-    }
-
-    /**
-     * Returns a {@link BitmapInfo} for the app-badge that is shown on top of each bubble. This
-     * will include the workprofile indicator on the badge if appropriate.
-     */
-    BitmapInfo getBadgeBitmap(Drawable userBadgedAppIcon, boolean isImportantConversation) {
-        if (userBadgedAppIcon instanceof AdaptiveIconDrawable) {
-            AdaptiveIconDrawable ad = (AdaptiveIconDrawable) userBadgedAppIcon;
-            userBadgedAppIcon = new CircularAdaptiveIcon(ad.getBackground(), ad.getForeground());
-        }
-        if (isImportantConversation) {
-            userBadgedAppIcon = new CircularRingDrawable(userBadgedAppIcon);
-        }
-        Bitmap userBadgedBitmap = createIconBitmap(
-                userBadgedAppIcon, 1, MODE_WITH_SHADOW);
-        return createIconBitmap(userBadgedBitmap);
-    }
-
-    private class CircularRingDrawable extends CircularAdaptiveIcon {
-
-        final int mImportantConversationColor;
-        final int mRingWidth;
-        final Rect mInnerBounds = new Rect();
-
-        final Drawable mDr;
-
-        CircularRingDrawable(Drawable dr) {
-            super(null, null);
-            mDr = dr;
-            mImportantConversationColor = mContext.getResources().getColor(
-                    R.color.important_conversation, null);
-            mRingWidth = mContext.getResources().getDimensionPixelSize(
-                    com.android.internal.R.dimen.importance_ring_stroke_width);
-        }
-
-        @Override
-        public void draw(Canvas canvas) {
-            int save = canvas.save();
-            canvas.clipPath(getIconMask());
-            canvas.drawColor(mImportantConversationColor);
-            mInnerBounds.set(getBounds());
-            mInnerBounds.inset(mRingWidth, mRingWidth);
-            canvas.translate(mInnerBounds.left, mInnerBounds.top);
-            mDr.setBounds(0, 0, mInnerBounds.width(), mInnerBounds.height());
-            mDr.draw(canvas);
-            canvas.restoreToCount(save);
-        }
-    }
-
-    private static class CircularAdaptiveIcon extends AdaptiveIconDrawable {
-
-        final Path mPath = new Path();
-
-        CircularAdaptiveIcon(Drawable bg, Drawable fg) {
-            super(bg, fg);
-        }
-
-        @Override
-        public Path getIconMask() {
-            mPath.reset();
-            Rect bounds = getBounds();
-            mPath.addOval(bounds.left, bounds.top, bounds.right, bounds.bottom, Path.Direction.CW);
-            return mPath;
-        }
-
-        @Override
-        public void draw(Canvas canvas) {
-            int save = canvas.save();
-            canvas.clipPath(getIconMask());
-
-            Drawable d;
-            if ((d = getBackground()) != null) {
-                d.draw(canvas);
-            }
-            if ((d = getForeground()) != null) {
-                d.draw(canvas);
-            }
-            canvas.restoreToCount(save);
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index da8eb47..fd66153 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -89,6 +89,9 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.launcher3.icons.BubbleBadgeIconFactory;
+import com.android.launcher3.icons.BubbleIconFactory;
+import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
@@ -317,8 +320,13 @@
         mBubblePositioner = positioner;
         mBubbleData = data;
         mSavedUserBubbleData = new SparseArray<>();
-        mBubbleIconFactory = new BubbleIconFactory(context);
-        mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(context);
+        mBubbleIconFactory = new BubbleIconFactory(context,
+                context.getResources().getDimensionPixelSize(R.dimen.bubble_size));
+        mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(context,
+                context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
+                context.getResources().getColor(R.color.important_conversation),
+                context.getResources().getDimensionPixelSize(
+                        com.android.internal.R.dimen.importance_ring_stroke_width));
         mDisplayController = displayController;
         mTaskViewTransitions = taskViewTransitions;
         mOneHandedOptional = oneHandedOptional;
@@ -927,8 +935,13 @@
         if (mStackView != null) {
             mStackView.onThemeChanged();
         }
-        mBubbleIconFactory = new BubbleIconFactory(mContext);
-        mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext);
+        mBubbleIconFactory = new BubbleIconFactory(mContext,
+                mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size));
+        mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext,
+                mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
+                mContext.getResources().getColor(R.color.important_conversation),
+                mContext.getResources().getDimensionPixelSize(
+                        com.android.internal.R.dimen.importance_ring_stroke_width));
 
         // Reload each bubble
         for (Bubble b : mBubbleData.getBubbles()) {
@@ -964,8 +977,13 @@
                 mDensityDpi = newConfig.densityDpi;
                 mScreenBounds.set(newConfig.windowConfiguration.getBounds());
                 mBubbleData.onMaxBubblesChanged();
-                mBubbleIconFactory = new BubbleIconFactory(mContext);
-                mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext);
+                mBubbleIconFactory = new BubbleIconFactory(mContext,
+                        mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size));
+                mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext,
+                        mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
+                        mContext.getResources().getColor(R.color.important_conversation),
+                        mContext.getResources().getDimensionPixelSize(
+                                com.android.internal.R.dimen.importance_ring_stroke_width));
                 mStackView.onDisplaySizeChanged();
             }
             if (newConfig.fontScale != mFontScale) {
@@ -1052,7 +1070,27 @@
      * Expands and selects the provided bubble as long as it already exists in the stack or the
      * overflow.
      *
-     * This is currently only used when opening a bubble via clicking on a conversation widget.
+     * This is used by external callers (launcher).
+     */
+    public void expandStackAndSelectBubbleFromLauncher(String key) {
+        Bubble b = mBubbleData.getAnyBubbleWithkey(key);
+        if (b == null) {
+            return;
+        }
+        if (mBubbleData.hasBubbleInStackWithKey(b.getKey())) {
+            // already in the stack
+            mBubbleData.setSelectedBubbleFromLauncher(b);
+            mLayerView.showExpandedView(b);
+        } else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) {
+            // TODO: (b/271468319) handle overflow
+        } else {
+            Log.w(TAG, "didn't add bubble from launcher: " + key);
+        }
+    }
+
+    /**
+     * Expands and selects the provided bubble as long as it already exists in the stack or the
+     * overflow. This is currently used when opening a bubble via clicking on a conversation widget.
      */
     public void expandStackAndSelectBubble(Bubble b) {
         if (b == null) {
@@ -1703,6 +1741,14 @@
 
             // Update the cached state for queries from SysUI
             mImpl.mCachedState.update(update);
+
+            if (isShowingAsBubbleBar() && mBubbleStateListener != null) {
+                BubbleBarUpdate bubbleBarUpdate = update.toBubbleBarUpdate();
+                // Some updates aren't relevant to the bubble bar so check first.
+                if (bubbleBarUpdate.anythingChanged()) {
+                    mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate);
+                }
+            }
         }
     };
 
@@ -1972,17 +2018,20 @@
 
         @Override
         public void showBubble(String key, boolean onLauncherHome) {
-            // TODO
+            mMainExecutor.execute(() -> {
+                mBubblePositioner.setShowingInBubbleBar(onLauncherHome);
+                mController.expandStackAndSelectBubbleFromLauncher(key);
+            });
         }
 
         @Override
         public void removeBubble(String key, int reason) {
-            // TODO
+            // TODO (b/271466616) allow removals from launcher
         }
 
         @Override
         public void collapseBubbles() {
-            // TODO
+            mMainExecutor.execute(() -> mController.collapseStack());
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index a26c0c4..f9cf9d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -334,6 +334,35 @@
         dispatchPendingChanges();
     }
 
+    /**
+     * Sets the selected bubble and expands it, but doesn't dispatch changes
+     * to {@link BubbleData.Listener}. This is used for updates coming from launcher whose views
+     * will already be updated so we don't need to notify them again, but BubbleData should be
+     * updated to have the correct state.
+     */
+    public void setSelectedBubbleFromLauncher(BubbleViewProvider bubble) {
+        if (DEBUG_BUBBLE_DATA) {
+            Log.d(TAG, "setSelectedBubbleFromLauncher: " + bubble);
+        }
+        mExpanded = true;
+        if (Objects.equals(bubble, mSelectedBubble)) {
+            return;
+        }
+        boolean isOverflow = bubble != null && BubbleOverflow.KEY.equals(bubble.getKey());
+        if (bubble != null
+                && !mBubbles.contains(bubble)
+                && !mOverflowBubbles.contains(bubble)
+                && !isOverflow) {
+            Log.e(TAG, "Cannot select bubble which doesn't exist!"
+                    + " (" + bubble + ") bubbles=" + mBubbles);
+            return;
+        }
+        if (bubble != null && !isOverflow) {
+            ((Bubble) bubble).markAsAccessedAt(mTimeSource.currentTimeMillis());
+        }
+        mSelectedBubble = bubble;
+    }
+
     public void setSelectedBubble(BubbleViewProvider bubble) {
         if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "setSelectedBubble: " + bubble);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
deleted file mode 100644
index 4ded3ea..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
+++ /dev/null
@@ -1,84 +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.wm.shell.bubbles;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.LauncherApps;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.launcher3.icons.BaseIconFactory;
-import com.android.wm.shell.R;
-
-/**
- * Factory for creating normalized bubble icons.
- * We are not using Launcher's IconFactory because bubbles only runs on the UI thread,
- * so there is no need to manage a pool across multiple threads.
- */
-@VisibleForTesting
-public class BubbleIconFactory extends BaseIconFactory {
-
-    public BubbleIconFactory(Context context) {
-        super(context, context.getResources().getConfiguration().densityDpi,
-                context.getResources().getDimensionPixelSize(R.dimen.bubble_size));
-    }
-
-    /**
-     * Returns the drawable that the developer has provided to display in the bubble.
-     */
-    Drawable getBubbleDrawable(@NonNull final Context context,
-            @Nullable final ShortcutInfo shortcutInfo, @Nullable final Icon ic) {
-        if (shortcutInfo != null) {
-            LauncherApps launcherApps =
-                    (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
-            int density = context.getResources().getConfiguration().densityDpi;
-            return launcherApps.getShortcutIconDrawable(shortcutInfo, density);
-        } else {
-            if (ic != null) {
-                if (ic.getType() == Icon.TYPE_URI
-                        || ic.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
-                    context.grantUriPermission(context.getPackageName(),
-                            ic.getUri(),
-                            Intent.FLAG_GRANT_READ_URI_PERMISSION);
-                }
-                return ic.loadDrawable(context);
-            }
-            return null;
-        }
-    }
-
-    /**
-     * Creates the bitmap for the provided drawable and returns the scale used for
-     * drawing the actual drawable.
-     */
-    public Bitmap createIconBitmap(@NonNull Drawable icon, float[] outScale) {
-        if (outScale == null) {
-            outScale = new float[1];
-        }
-        icon = normalizeAndWrapToAdaptiveIcon(icon,
-                true /* shrinkNonAdaptiveIcons */,
-                null /* outscale */,
-                outScale);
-        return createIconBitmap(icon, outScale[0], MODE_WITH_SHADOW);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 6cdb80b..c2a05b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -28,6 +28,7 @@
 import android.util.TypedValue
 import android.view.LayoutInflater
 import android.widget.FrameLayout
+import com.android.launcher3.icons.BubbleIconFactory
 import com.android.wm.shell.R
 import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView
 
@@ -93,7 +94,8 @@
         val shapeColor = res.getColor(android.R.color.system_accent1_1000)
         overflowBtn?.iconDrawable?.setTint(shapeColor)
 
-        val iconFactory = BubbleIconFactory(context)
+        val iconFactory = BubbleIconFactory(context,
+                context.getResources().getDimensionPixelSize(R.dimen.bubble_size))
 
         // Update bitmap
         val fg = InsetDrawable(overflowBtn?.iconDrawable, overflowIconInset)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 1a97c05..d1081de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -42,6 +42,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
 import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.BubbleBadgeIconFactory;
+import com.android.launcher3.icons.BubbleIconFactory;
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
 import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
index a7171fd..82fe38c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -366,11 +367,8 @@
         mContext = context;
         mPipDisplayLayoutState = pipDisplayLayoutState;
 
-        boolean enablePipSizeLargeScreen = SystemProperties
-                .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true);
-
         // choose between two implementations of size spec logic
-        if (enablePipSizeLargeScreen) {
+        if (supportsPipSizeLargeScreen()) {
             mSizeSpecSourceImpl = new SizeSpecLargeScreenOptimizedImpl();
         } else {
             mSizeSpecSourceImpl = new SizeSpecDefaultImpl();
@@ -515,6 +513,18 @@
         }
     }
 
+    @VisibleForTesting
+    boolean supportsPipSizeLargeScreen() {
+        // TODO(b/271468706): switch Tv to having a dedicated SizeSpecSource once the SizeSpecSource
+        // can be injected
+        return SystemProperties
+                .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true) && !isTv();
+    }
+
+    private boolean isTv() {
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+    }
+
     /** Dumps internal state. */
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index dd91a37..04cb17c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -690,6 +690,10 @@
         if (options1 == null) options1 = new Bundle();
         if (taskId2 == INVALID_TASK_ID) {
             // Launching a solo task.
+            // Exit split first if this task under split roots.
+            if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
+                exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+            }
             ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
             activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
             options1 = activityOptions.toBundle();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 060dc4e..dfde7e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -110,19 +110,11 @@
         final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
         final WindowContainerTransaction wct = new WindowContainerTransaction();
 
-        final int outsetLeftId = R.dimen.freeform_resize_handle;
-        final int outsetTopId = R.dimen.freeform_resize_handle;
-        final int outsetRightId = R.dimen.freeform_resize_handle;
-        final int outsetBottomId = R.dimen.freeform_resize_handle;
-
         mRelayoutParams.reset();
         mRelayoutParams.mRunningTaskInfo = taskInfo;
         mRelayoutParams.mLayoutResId = R.layout.caption_window_decor;
         mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
         mRelayoutParams.mShadowRadiusId = shadowRadiusID;
-        if (isDragResizeable) {
-            mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
-        }
 
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
         // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index f998217..afc573e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -197,7 +197,7 @@
 
     @Override
     public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
-        if (mTransitionPausingRelayout.equals(merged)) {
+        if (merged.equals(mTransitionPausingRelayout)) {
             mTransitionPausingRelayout = playing;
         }
     }
@@ -312,8 +312,12 @@
             } else if (id == R.id.back_button) {
                 mTaskOperations.injectBackKey();
             } else if (id == R.id.caption_handle || id == R.id.open_menu_button) {
-                moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
-                decoration.createHandleMenu();
+                if (!decoration.isHandleMenuActive()) {
+                    moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
+                    decoration.createHandleMenu();
+                } else {
+                    decoration.closeHandleMenu();
+                }
             } else if (id == R.id.desktop_button) {
                 mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
                 mDesktopTasksController.ifPresent(c -> c.moveToDesktop(mTaskId));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index efc90b5..a004e37 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -208,11 +208,6 @@
         final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
         final WindowContainerTransaction wct = new WindowContainerTransaction();
 
-        final int outsetLeftId = R.dimen.freeform_resize_handle;
-        final int outsetTopId = R.dimen.freeform_resize_handle;
-        final int outsetRightId = R.dimen.freeform_resize_handle;
-        final int outsetBottomId = R.dimen.freeform_resize_handle;
-
         final int windowDecorLayoutId = getDesktopModeWindowDecorLayoutId(
                 taskInfo.getWindowingMode());
         mRelayoutParams.reset();
@@ -220,9 +215,6 @@
         mRelayoutParams.mLayoutResId = windowDecorLayoutId;
         mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
         mRelayoutParams.mShadowRadiusId = shadowRadiusID;
-        if (isDragResizeable) {
-            mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
-        }
 
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
         // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -424,13 +416,12 @@
         if (mRelayoutParams.mLayoutResId
                 == R.layout.desktop_mode_app_controls_window_decor) {
             // Align the handle menu to the left of the caption.
-            menuX = mRelayoutParams.mCaptionX - mResult.mDecorContainerOffsetX + mMarginMenuStart;
-            menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuTop;
+            menuX = mRelayoutParams.mCaptionX + mMarginMenuStart;
+            menuY = mRelayoutParams.mCaptionY + mMarginMenuTop;
         } else {
             // Position the handle menu at the center of the caption.
-            menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2)
-                    - mResult.mDecorContainerOffsetX;
-            menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuStart;
+            menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2);
+            menuY = mRelayoutParams.mCaptionY + mMarginMenuStart;
         }
 
         // App Info pill setup.
@@ -487,26 +478,30 @@
         if (mHandleMenuAppInfoPill.mWindowViewHost.getView().getWidth() == 0) return;
 
         PointF inputPoint = offsetCaptionLocation(ev);
+
+        // If this is called before open_menu_button's onClick, we don't want to close
+        // the menu since it will just reopen in onClick.
+        final boolean pointInOpenMenuButton = pointInView(
+                mResult.mRootView.findViewById(R.id.open_menu_button),
+                inputPoint.x,
+                inputPoint.y);
+
         final boolean pointInAppInfoPill = pointInView(
                 mHandleMenuAppInfoPill.mWindowViewHost.getView(),
-                inputPoint.x - mHandleMenuAppInfoPillPosition.x - mResult.mDecorContainerOffsetX,
-                inputPoint.y - mHandleMenuAppInfoPillPosition.y
-                        - mResult.mDecorContainerOffsetY);
+                inputPoint.x - mHandleMenuAppInfoPillPosition.x,
+                inputPoint.y - mHandleMenuAppInfoPillPosition.y);
         boolean pointInWindowingPill = false;
         if (mHandleMenuWindowingPill != null) {
             pointInWindowingPill = pointInView(mHandleMenuWindowingPill.mWindowViewHost.getView(),
-                    inputPoint.x - mHandleMenuWindowingPillPosition.x
-                            - mResult.mDecorContainerOffsetX,
-                    inputPoint.y - mHandleMenuWindowingPillPosition.y
-                            - mResult.mDecorContainerOffsetY);
+                    inputPoint.x - mHandleMenuWindowingPillPosition.x,
+                    inputPoint.y - mHandleMenuWindowingPillPosition.y);
         }
         final boolean pointInMoreActionsPill = pointInView(
                 mHandleMenuMoreActionsPill.mWindowViewHost.getView(),
-                inputPoint.x - mHandleMenuMoreActionsPillPosition.x
-                        - mResult.mDecorContainerOffsetX,
-                inputPoint.y - mHandleMenuMoreActionsPillPosition.y
-                        - mResult.mDecorContainerOffsetY);
-        if (!pointInAppInfoPill && !pointInWindowingPill && !pointInMoreActionsPill) {
+                inputPoint.x - mHandleMenuMoreActionsPillPosition.x,
+                inputPoint.y - mHandleMenuMoreActionsPillPosition.y);
+        if (!pointInAppInfoPill && !pointInWindowingPill
+                && !pointInMoreActionsPill && !pointInOpenMenuButton) {
             closeHandleMenu();
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 8cb575c..d5437c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -64,8 +64,8 @@
     private final TaskResizeInputEventReceiver mInputEventReceiver;
     private final DragPositioningCallback mCallback;
 
-    private int mWidth;
-    private int mHeight;
+    private int mTaskWidth;
+    private int mTaskHeight;
     private int mResizeHandleThickness;
     private int mCornerSize;
 
@@ -128,78 +128,84 @@
      * This is also used to update the touch regions of this handler every event dispatched here is
      * a potential resize request.
      *
-     * @param width The width of the drag resize handler in pixels, including resize handle
-     *              thickness. That is task width + 2 * resize handle thickness.
-     * @param height The height of the drag resize handler in pixels, including resize handle
-     *               thickness. That is task height + 2 * resize handle thickness.
+     * @param taskWidth The width of the task.
+     * @param taskHeight The height of the task.
      * @param resizeHandleThickness The thickness of the resize handle in pixels.
      * @param cornerSize The size of the resize handle centered in each corner.
      * @param touchSlop The distance in pixels user has to drag with touch for it to register as
      *                  a resize action.
      */
-    void setGeometry(int width, int height, int resizeHandleThickness, int cornerSize,
+    void setGeometry(int taskWidth, int taskHeight, int resizeHandleThickness, int cornerSize,
             int touchSlop) {
-        if (mWidth == width && mHeight == height
+        if (mTaskWidth == taskWidth && mTaskHeight == taskHeight
                 && mResizeHandleThickness == resizeHandleThickness
                 && mCornerSize == cornerSize) {
             return;
         }
 
-        mWidth = width;
-        mHeight = height;
+        mTaskWidth = taskWidth;
+        mTaskHeight = taskHeight;
         mResizeHandleThickness = resizeHandleThickness;
         mCornerSize = cornerSize;
         mDragDetector.setTouchSlop(touchSlop);
 
         Region touchRegion = new Region();
-        final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness);
+        final Rect topInputBounds = new Rect(
+                -mResizeHandleThickness,
+                -mResizeHandleThickness,
+                mTaskWidth + mResizeHandleThickness,
+                0);
         touchRegion.union(topInputBounds);
 
-        final Rect leftInputBounds = new Rect(0, mResizeHandleThickness,
-                mResizeHandleThickness, mHeight - mResizeHandleThickness);
+        final Rect leftInputBounds = new Rect(
+                -mResizeHandleThickness,
+                0,
+                0,
+                mTaskHeight);
         touchRegion.union(leftInputBounds);
 
         final Rect rightInputBounds = new Rect(
-                mWidth - mResizeHandleThickness, mResizeHandleThickness,
-                mWidth, mHeight - mResizeHandleThickness);
+                mTaskWidth,
+                0,
+                mTaskWidth + mResizeHandleThickness,
+                mTaskHeight);
         touchRegion.union(rightInputBounds);
 
-        final Rect bottomInputBounds = new Rect(0, mHeight - mResizeHandleThickness,
-                mWidth, mHeight);
+        final Rect bottomInputBounds = new Rect(
+                -mResizeHandleThickness,
+                mTaskHeight,
+                mTaskWidth + mResizeHandleThickness,
+                mTaskHeight + mResizeHandleThickness);
         touchRegion.union(bottomInputBounds);
 
         // Set up touch areas in each corner.
         int cornerRadius = mCornerSize / 2;
         mLeftTopCornerBounds = new Rect(
-                mResizeHandleThickness - cornerRadius,
-                mResizeHandleThickness - cornerRadius,
-                mResizeHandleThickness + cornerRadius,
-                mResizeHandleThickness + cornerRadius
-        );
+                -cornerRadius,
+                -cornerRadius,
+                cornerRadius,
+                cornerRadius);
         touchRegion.union(mLeftTopCornerBounds);
 
         mRightTopCornerBounds = new Rect(
-                mWidth - mResizeHandleThickness - cornerRadius,
-                mResizeHandleThickness - cornerRadius,
-                mWidth - mResizeHandleThickness + cornerRadius,
-                mResizeHandleThickness + cornerRadius
-        );
+                mTaskWidth - cornerRadius,
+                -cornerRadius,
+                mTaskWidth + cornerRadius,
+                cornerRadius);
         touchRegion.union(mRightTopCornerBounds);
 
         mLeftBottomCornerBounds = new Rect(
-                mResizeHandleThickness - cornerRadius,
-                mHeight - mResizeHandleThickness - cornerRadius,
-                mResizeHandleThickness + cornerRadius,
-                mHeight - mResizeHandleThickness + cornerRadius
-        );
+                -cornerRadius,
+                mTaskHeight - cornerRadius,
+                cornerRadius,
+                mTaskHeight + cornerRadius);
         touchRegion.union(mLeftBottomCornerBounds);
 
         mRightBottomCornerBounds = new Rect(
-                mWidth - mResizeHandleThickness - cornerRadius,
-                mHeight - mResizeHandleThickness - cornerRadius,
-                mWidth - mResizeHandleThickness + cornerRadius,
-                mHeight - mResizeHandleThickness + cornerRadius
-        );
+                mTaskWidth - cornerRadius,
+                mTaskHeight - cornerRadius,
+                mTaskWidth + cornerRadius,
+                mTaskHeight + cornerRadius);
         touchRegion.union(mRightBottomCornerBounds);
 
         try {
@@ -358,16 +364,16 @@
         @TaskPositioner.CtrlType
         private int calculateResizeHandlesCtrlType(float x, float y) {
             int ctrlType = 0;
-            if (x < mResizeHandleThickness) {
+            if (x < 0) {
                 ctrlType |= TaskPositioner.CTRL_TYPE_LEFT;
             }
-            if (x > mWidth - mResizeHandleThickness) {
+            if (x > mTaskWidth) {
                 ctrlType |= TaskPositioner.CTRL_TYPE_RIGHT;
             }
-            if (y < mResizeHandleThickness) {
+            if (y < 0) {
                 ctrlType |= TaskPositioner.CTRL_TYPE_TOP;
             }
-            if (y > mHeight - mResizeHandleThickness) {
+            if (y > mTaskHeight) {
                 ctrlType |= TaskPositioner.CTRL_TYPE_BOTTOM;
             }
             return ctrlType;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 4ebd09f..bc5fd4d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -98,7 +98,6 @@
 
     private final Binder mOwner = new Binder();
     private final Rect mCaptionInsetsRect = new Rect();
-    private final Rect mTaskSurfaceCrop = new Rect();
     private final float[] mTmpColor = new float[3];
 
     WindowDecoration(
@@ -218,21 +217,14 @@
 
         final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
         final Resources resources = mDecorWindowContext.getResources();
-        outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
-        outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
-        outResult.mWidth = taskBounds.width()
-                + loadDimensionPixelSize(resources, params.mOutsetRightId)
-                - outResult.mDecorContainerOffsetX;
-        outResult.mHeight = taskBounds.height()
-                + loadDimensionPixelSize(resources, params.mOutsetBottomId)
-                - outResult.mDecorContainerOffsetY;
-        startT.setPosition(
-                        mDecorationContainerSurface,
-                        outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY)
-                .setWindowCrop(mDecorationContainerSurface,
-                        outResult.mWidth, outResult.mHeight)
+        outResult.mWidth = taskBounds.width();
+        outResult.mHeight = taskBounds.height();
+        startT.setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
                 .show(mDecorationContainerSurface);
 
+        // TODO(b/270202228): This surface can be removed. Instead, use
+        //  |mDecorationContainerSurface| to set the background now that it no longer has outsets
+        //  and its crop is set to the task bounds.
         // TaskBackgroundSurface
         if (mTaskBackgroundSurface == null) {
             final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
@@ -250,8 +242,7 @@
         mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
         mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
         mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
-        startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(),
-                        taskBounds.height())
+        startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height())
                 .setShadowRadius(mTaskBackgroundSurface, shadowRadius)
                 .setColor(mTaskBackgroundSurface, mTmpColor)
                 .show(mTaskBackgroundSurface);
@@ -269,11 +260,7 @@
         final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
         final int captionWidth = taskBounds.width();
 
-        startT.setPosition(
-                        mCaptionContainerSurface,
-                        -outResult.mDecorContainerOffsetX + params.mCaptionX,
-                        -outResult.mDecorContainerOffsetY + params.mCaptionY)
-                .setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
+        startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
                 .show(mCaptionContainerSurface);
 
         if (mCaptionWindowManager == null) {
@@ -314,14 +301,9 @@
 
         // Task surface itself
         Point taskPosition = mTaskInfo.positionInParent;
-        mTaskSurfaceCrop.set(
-                outResult.mDecorContainerOffsetX,
-                outResult.mDecorContainerOffsetY,
-                outResult.mWidth + outResult.mDecorContainerOffsetX,
-                outResult.mHeight + outResult.mDecorContainerOffsetY);
         startT.show(mTaskSurface);
         finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
-                .setCrop(mTaskSurface, mTaskSurfaceCrop);
+                .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
     }
 
     /**
@@ -447,37 +429,15 @@
         int mCaptionWidthId;
         int mShadowRadiusId;
 
-        int mOutsetTopId;
-        int mOutsetBottomId;
-        int mOutsetLeftId;
-        int mOutsetRightId;
-
         int mCaptionX;
         int mCaptionY;
 
-        void setOutsets(int leftId, int topId, int rightId, int bottomId) {
-            mOutsetLeftId = leftId;
-            mOutsetTopId = topId;
-            mOutsetRightId = rightId;
-            mOutsetBottomId = bottomId;
-        }
-
-        void setCaptionPosition(int left, int top) {
-            mCaptionX = left;
-            mCaptionY = top;
-        }
-
         void reset() {
             mLayoutResId = Resources.ID_NULL;
             mCaptionHeightId = Resources.ID_NULL;
             mCaptionWidthId = Resources.ID_NULL;
             mShadowRadiusId = Resources.ID_NULL;
 
-            mOutsetTopId = Resources.ID_NULL;
-            mOutsetBottomId = Resources.ID_NULL;
-            mOutsetLeftId = Resources.ID_NULL;
-            mOutsetRightId = Resources.ID_NULL;
-
             mCaptionX = 0;
             mCaptionY = 0;
         }
@@ -487,14 +447,10 @@
         int mWidth;
         int mHeight;
         T mRootView;
-        int mDecorContainerOffsetX;
-        int mDecorContainerOffsetY;
 
         void reset() {
             mWidth = 0;
             mHeight = 0;
-            mDecorContainerOffsetX = 0;
-            mDecorContainerOffsetY = 0;
             mRootView = null;
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index e986ee1..c416ad0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -137,7 +137,7 @@
     portraitPosTop: Boolean
 ) {
     assertLayers {
-        this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
+        this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
             .then()
             .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
             .then()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
index 390c830..425bbf0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
@@ -18,7 +18,6 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.when;
@@ -76,7 +75,7 @@
     @Mock private Resources mResources;
 
     private PipDisplayLayoutState mPipDisplayLayoutState;
-    private PipSizeSpecHandler mPipSizeSpecHandler;
+    private TestPipSizeSpecHandler mPipSizeSpecHandler;
 
     /**
      * Sets up static Mockito session for SystemProperties and mocks necessary static methods.
@@ -84,8 +83,6 @@
     private static void setUpStaticSystemPropertiesSession() {
         sStaticMockitoSession = mockitoSession()
                 .mockStatic(SystemProperties.class).startMocking();
-        // make sure the feature flag is on
-        when(SystemProperties.getBoolean(anyString(), anyBoolean())).thenReturn(true);
         when(SystemProperties.get(anyString(), anyString())).thenAnswer(invocation -> {
             String property = invocation.getArgument(0);
             if (property.equals("com.android.wm.shell.pip.phone.def_percentage")) {
@@ -161,7 +158,7 @@
         mPipDisplayLayoutState.setDisplayLayout(displayLayout);
 
         setUpStaticSystemPropertiesSession();
-        mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+        mPipSizeSpecHandler = new TestPipSizeSpecHandler(mContext, mPipDisplayLayoutState);
 
         // no overridden min edge size by default
         mPipSizeSpecHandler.setOverrideMinSize(null);
@@ -214,4 +211,16 @@
 
         Assert.assertEquals(expectedSize, actualSize);
     }
+
+    static class TestPipSizeSpecHandler extends PipSizeSpecHandler {
+
+        TestPipSizeSpecHandler(Context context, PipDisplayLayoutState displayLayoutState) {
+            super(context, displayLayoutState);
+        }
+
+        @Override
+        boolean supportsPipSizeLargeScreen() {
+            return true;
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index dfa3c10..e8147ff 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -159,14 +159,8 @@
                 .setVisible(false)
                 .build();
         taskInfo.isFocused = false;
-        // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
-        // 64px.
+        // Density is 2. Shadow radius is 10px. Caption height is 64px.
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mRelayoutParams.setOutsets(
-                R.dimen.test_window_decor_left_outset,
-                R.dimen.test_window_decor_top_outset,
-                R.dimen.test_window_decor_right_outset,
-                R.dimen.test_window_decor_bottom_outset);
 
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -213,14 +207,8 @@
                 .setVisible(true)
                 .build();
         taskInfo.isFocused = true;
-        // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
-        // 64px.
+        // Density is 2. Shadow radius is 10px. Caption height is 64px.
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mRelayoutParams.setOutsets(
-                R.dimen.test_window_decor_left_outset,
-                R.dimen.test_window_decor_top_outset,
-                R.dimen.test_window_decor_right_outset,
-                R.dimen.test_window_decor_bottom_outset);
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
 
@@ -229,8 +217,7 @@
         verify(decorContainerSurfaceBuilder).setParent(taskSurface);
         verify(decorContainerSurfaceBuilder).setContainerLayer();
         verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
-        verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -20, -40);
-        verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 380, 220);
+        verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100);
 
         verify(taskBackgroundSurfaceBuilder).setParent(taskSurface);
         verify(taskBackgroundSurfaceBuilder).setEffectLayer();
@@ -244,7 +231,6 @@
 
         verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
         verify(captionContainerSurfaceBuilder).setContainerLayer();
-        verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
         verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
         verify(mMockSurfaceControlStartT).show(captionContainerSurface);
 
@@ -268,12 +254,12 @@
         verify(mMockSurfaceControlFinishT)
                 .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
         verify(mMockSurfaceControlFinishT)
-                .setCrop(taskSurface, new Rect(-20, -40, 360, 180));
+                .setWindowCrop(taskSurface, 300, 100);
         verify(mMockSurfaceControlStartT)
                 .show(taskSurface);
 
-        assertEquals(380, mRelayoutResult.mWidth);
-        assertEquals(220, mRelayoutResult.mHeight);
+        assertEquals(300, mRelayoutResult.mWidth);
+        assertEquals(100, mRelayoutResult.mHeight);
     }
 
     @Test
@@ -309,14 +295,8 @@
                 .setVisible(true)
                 .build();
         taskInfo.isFocused = true;
-        // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
-        // 64px.
+        // Density is 2. Shadow radius is 10px. Caption height is 64px.
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mRelayoutParams.setOutsets(
-                R.dimen.test_window_decor_left_outset,
-                R.dimen.test_window_decor_top_outset,
-                R.dimen.test_window_decor_right_outset,
-                R.dimen.test_window_decor_bottom_outset);
 
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -419,11 +399,6 @@
                 .build();
         taskInfo.isFocused = true;
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mRelayoutParams.setOutsets(
-                R.dimen.test_window_decor_left_outset,
-                R.dimen.test_window_decor_top_outset,
-                R.dimen.test_window_decor_right_outset,
-                R.dimen.test_window_decor_bottom_outset);
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
         windowDecor.relayout(taskInfo);
@@ -438,7 +413,7 @@
         verify(additionalWindowSurfaceBuilder).setContainerLayer();
         verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface);
         verify(additionalWindowSurfaceBuilder).build();
-        verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40);
+        verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 0, 0);
         final int width = WindowDecoration.loadDimensionPixelSize(
                 mContext.getResources(), mCaptionMenuWidthId);
         final int height = WindowDecoration.loadDimensionPixelSize(
@@ -496,11 +471,6 @@
                 .build();
         taskInfo.isFocused = true;
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mRelayoutParams.setOutsets(
-                R.dimen.test_window_decor_left_outset,
-                R.dimen.test_window_decor_top_outset,
-                R.dimen.test_window_decor_right_outset,
-                R.dimen.test_window_decor_bottom_outset);
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
 
@@ -508,7 +478,6 @@
 
         verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
         verify(captionContainerSurfaceBuilder).setContainerLayer();
-        verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
         // Width of the captionContainerSurface should match the width of TASK_BOUNDS
         verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
         verify(mMockSurfaceControlStartT).show(captionContainerSurface);
@@ -584,9 +553,7 @@
             String name = "Test Window";
             WindowDecoration.AdditionalWindow additionalWindow =
                     addWindow(R.layout.desktop_mode_window_decor_handle_menu_app_info_pill, name,
-                            mMockSurfaceControlAddWindowT,
-                            x - mRelayoutResult.mDecorContainerOffsetX,
-                            y - mRelayoutResult.mDecorContainerOffsetY,
+                            mMockSurfaceControlAddWindowT, x, y,
                             width, height, shadowRadius, cornerRadius);
             return additionalWindow;
         }
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index d08bc5c5..8049dc9 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -29,9 +29,10 @@
 
 namespace android {
 
-AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed)
-        : mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed) {
-    mTimeToShowNextSnapshot = ms2ns(mSkAnimatedImage->currentFrameDuration());
+AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed,
+                                             SkEncodedImageFormat format)
+        : mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed), mFormat(format) {
+    mTimeToShowNextSnapshot = ms2ns(currentFrameDuration());
     setStagingBounds(mSkAnimatedImage->getBounds());
 }
 
@@ -92,7 +93,7 @@
         // directly from mSkAnimatedImage.
         lock.unlock();
         std::unique_lock imageLock{mImageLock};
-        *outDelay = ms2ns(mSkAnimatedImage->currentFrameDuration());
+        *outDelay = ms2ns(currentFrameDuration());
         return true;
     } else {
         // The next snapshot has not yet been decoded, but we've already passed
@@ -109,7 +110,7 @@
     Snapshot snap;
     {
         std::unique_lock lock{mImageLock};
-        snap.mDurationMS = mSkAnimatedImage->decodeNextFrame();
+        snap.mDurationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame());
         snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
     }
 
@@ -123,7 +124,7 @@
         std::unique_lock lock{mImageLock};
         mSkAnimatedImage->reset();
         snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
-        snap.mDurationMS = mSkAnimatedImage->currentFrameDuration();
+        snap.mDurationMS = currentFrameDuration();
     }
 
     return snap;
@@ -274,7 +275,7 @@
         {
             std::unique_lock lock{mImageLock};
             mSkAnimatedImage->reset();
-            durationMS = mSkAnimatedImage->currentFrameDuration();
+            durationMS = currentFrameDuration();
         }
         {
             std::unique_lock lock{mSwapLock};
@@ -306,7 +307,7 @@
     {
         std::unique_lock lock{mImageLock};
         if (update) {
-            durationMS = mSkAnimatedImage->decodeNextFrame();
+            durationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame());
         }
 
         canvas->drawDrawable(mSkAnimatedImage.get());
@@ -336,4 +337,20 @@
     return SkRectMakeLargest();
 }
 
+int AnimatedImageDrawable::adjustFrameDuration(int durationMs) {
+    if (durationMs == SkAnimatedImage::kFinished) {
+        return SkAnimatedImage::kFinished;
+    }
+
+    if (mFormat == SkEncodedImageFormat::kGIF) {
+        // Match Chrome & Firefox behavior that gifs with a duration <= 10ms is bumped to 100ms
+        return durationMs <= 10 ? 100 : durationMs;
+    }
+    return durationMs;
+}
+
+int AnimatedImageDrawable::currentFrameDuration() {
+    return adjustFrameDuration(mSkAnimatedImage->currentFrameDuration());
+}
+
 }  // namespace android
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h
index 8ca3c7e..1e965ab 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.h
+++ b/libs/hwui/hwui/AnimatedImageDrawable.h
@@ -16,16 +16,16 @@
 
 #pragma once
 
-#include <cutils/compiler.h>
-#include <utils/Macros.h>
-#include <utils/RefBase.h>
-#include <utils/Timers.h>
-
 #include <SkAnimatedImage.h>
 #include <SkCanvas.h>
 #include <SkColorFilter.h>
 #include <SkDrawable.h>
+#include <SkEncodedImageFormat.h>
 #include <SkPicture.h>
+#include <cutils/compiler.h>
+#include <utils/Macros.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
 
 #include <future>
 #include <mutex>
@@ -48,7 +48,8 @@
 public:
     // bytesUsed includes the approximate sizes of the SkAnimatedImage and the SkPictures in the
     // Snapshots.
-    AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed);
+    AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed,
+                          SkEncodedImageFormat format);
 
     /**
      * This updates the internal time and returns true if the image needs
@@ -115,6 +116,7 @@
 private:
     sk_sp<SkAnimatedImage> mSkAnimatedImage;
     const size_t mBytesUsed;
+    const SkEncodedImageFormat mFormat;
 
     bool mRunning = false;
     bool mStarting = false;
@@ -157,6 +159,9 @@
     Properties mProperties;
 
     std::unique_ptr<OnAnimationEndListener> mEndListener;
+
+    int adjustFrameDuration(int);
+    int currentFrameDuration();
 };
 
 }  // namespace android
diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp
index 373e893..a7f5aa83 100644
--- a/libs/hwui/jni/AnimatedImageDrawable.cpp
+++ b/libs/hwui/jni/AnimatedImageDrawable.cpp
@@ -97,7 +97,7 @@
         bytesUsed += picture->approximateBytesUsed();
     }
 
-
+    SkEncodedImageFormat format = imageDecoder->mCodec->getEncodedFormat();
     sk_sp<SkAnimatedImage> animatedImg = SkAnimatedImage::Make(std::move(imageDecoder->mCodec),
                                                                info, subset,
                                                                std::move(picture));
@@ -108,8 +108,8 @@
 
     bytesUsed += sizeof(animatedImg.get());
 
-    sk_sp<AnimatedImageDrawable> drawable(new AnimatedImageDrawable(std::move(animatedImg),
-                                                                    bytesUsed));
+    sk_sp<AnimatedImageDrawable> drawable(
+            new AnimatedImageDrawable(std::move(animatedImg), bytesUsed, format));
     return reinterpret_cast<jlong>(drawable.release());
 }
 
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index cc987bc..c4d3f5c 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -53,6 +53,8 @@
 }
 
 MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
+    bool wasSurfaceless = mEglManager.isCurrent(EGL_NO_SURFACE);
+
     // In case the surface was destroyed (e.g. a previous trimMemory call) we
     // need to recreate it here.
     if (mHardwareBuffer) {
@@ -65,6 +67,37 @@
     if (!mEglManager.makeCurrent(mEglSurface, &error)) {
         return MakeCurrentResult::AlreadyCurrent;
     }
+
+    // Make sure read/draw buffer state of default framebuffer is GL_BACK. Vendor implementations
+    // disagree on the draw/read buffer state if the default framebuffer transitions from a surface
+    // to EGL_NO_SURFACE and vice-versa. There was a related discussion within Khronos on this topic.
+    // See https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13534.
+    // The discussion was not resolved with a clear consensus
+    if (error == 0 && wasSurfaceless && mEglSurface != EGL_NO_SURFACE) {
+        GLint curReadFB = 0;
+        GLint curDrawFB = 0;
+        glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &curReadFB);
+        glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &curDrawFB);
+
+        GLint buffer = GL_NONE;
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
+        glGetIntegerv(GL_DRAW_BUFFER0, &buffer);
+        if (buffer == GL_NONE) {
+            const GLenum drawBuffer = GL_BACK;
+            glDrawBuffers(1, &drawBuffer);
+        }
+
+        glGetIntegerv(GL_READ_BUFFER, &buffer);
+        if (buffer == GL_NONE) {
+            glReadBuffer(GL_BACK);
+        }
+
+        glBindFramebuffer(GL_READ_FRAMEBUFFER, curReadFB);
+        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, curDrawFB);
+
+        GL_CHECKPOINT(LOW);
+    }
+
     return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded;
 }
 
@@ -104,7 +137,8 @@
 
     GrBackendRenderTarget backendRT(frame.width(), frame.height(), 0, STENCIL_BUFFER_SIZE, fboInfo);
 
-    SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
+    SkSurfaceProps props(mColorMode == ColorMode::Default ? 0 : SkSurfaceProps::kAlwaysDither_Flag,
+                         kUnknown_SkPixelGeometry);
 
     SkASSERT(mRenderThread.getGrContext() != nullptr);
     sk_sp<SkSurface> surface;
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index c34f90a..10737e9 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -28,7 +28,6 @@
 #include <SkMultiPictureDocument.h>
 #include <SkOverdrawCanvas.h>
 #include <SkOverdrawColorFilter.h>
-#include <SkPaintFilterCanvas.h>
 #include <SkPicture.h>
 #include <SkPictureRecorder.h>
 #include <SkRect.h>
@@ -450,23 +449,6 @@
     }
 }
 
-class ForceDitherCanvas : public SkPaintFilterCanvas {
-public:
-    ForceDitherCanvas(SkCanvas* canvas) : SkPaintFilterCanvas(canvas) {}
-
-protected:
-    bool onFilter(SkPaint& paint) const override {
-        paint.setDither(true);
-        return true;
-    }
-
-    void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override {
-        // We unroll the drawable using "this" canvas, so that draw calls contained inside will
-        // get dithering applied
-        drawable->draw(this, matrix);
-    }
-};
-
 void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
                                const std::vector<sp<RenderNode>>& nodes, bool opaque,
                                const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
@@ -521,12 +503,6 @@
         canvas->clear(SK_ColorTRANSPARENT);
     }
 
-    std::optional<ForceDitherCanvas> forceDitherCanvas;
-    if (shouldForceDither()) {
-        forceDitherCanvas.emplace(canvas);
-        canvas = &forceDitherCanvas.value();
-    }
-
     if (1 == nodes.size()) {
         if (!nodes[0]->nothingToDraw()) {
             RenderNodeDrawable root(nodes[0].get(), canvas);
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 0763b06..befee89 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -98,8 +98,6 @@
 
     bool isCapturingSkp() const { return mCaptureMode != CaptureMode::None; }
 
-    virtual bool shouldForceDither() const { return mColorMode != ColorMode::Default; }
-
 private:
     void renderFrameImpl(const SkRect& clip,
                          const std::vector<sp<RenderNode>>& nodes, bool opaque,
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index f22652f..86096d5 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -203,11 +203,6 @@
     return nullptr;
 }
 
-bool SkiaVulkanPipeline::shouldForceDither() const {
-    if (mVkSurface && mVkSurface->isBeyond8Bit()) return false;
-    return SkiaPipeline::shouldForceDither();
-}
-
 void SkiaVulkanPipeline::onContextDestroyed() {
     if (mVkSurface) {
         vulkanManager().destroySurface(mVkSurface);
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index 01a93c7..284cde5 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -53,7 +53,6 @@
     void onStop() override;
     bool isSurfaceReady() override;
     bool isContextReady() override;
-    bool supportsExtendedRangeHdr() const override { return true; }
     void setTargetSdrHdrRatio(float ratio) override;
     const SkM44& getPixelSnapMatrix() const override;
 
@@ -64,8 +63,6 @@
 protected:
     void onContextDestroyed() override;
 
-    bool shouldForceDither() const override;
-
 private:
     renderthread::VulkanManager& vulkanManager();
     renderthread::VulkanSurface* mVkSurface = nullptr;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 1b9d41a7..f60c1f3 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -236,7 +236,6 @@
 
     if (mNativeSurface && !mNativeSurface->didSetExtraBuffers()) {
         setBufferCount(mNativeSurface->getNativeWindow());
-
     }
 
     mFrameNumber = 0;
@@ -301,10 +300,6 @@
 
 float CanvasContext::setColorMode(ColorMode mode) {
     if (mode != mColorMode) {
-        const bool isHdr = mode == ColorMode::Hdr || mode == ColorMode::Hdr10;
-        if (isHdr && !mRenderPipeline->supportsExtendedRangeHdr()) {
-            mode = ColorMode::WideColorGamut;
-        }
         mColorMode = mode;
         mRenderPipeline->setSurfaceColorProperties(mode);
         setupPipelineSurface();
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 4fb114b..94f35fd 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -423,6 +423,7 @@
     EGLint attribs[] = {EGL_NONE, EGL_NONE, EGL_NONE};
 
     EGLConfig config = mEglConfig;
+    bool overrideWindowDataSpaceForHdr = false;
     if (colorMode == ColorMode::A8) {
         // A8 doesn't use a color space
         if (!mEglConfigA8) {
@@ -450,12 +451,13 @@
                 case ColorMode::Default:
                     attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
                     break;
-                // Extended Range HDR requires being able to manipulate the dataspace in ways
-                // we cannot easily do while going through EGLSurface. Given this requires
-                // composer3 support, just treat HDR as equivalent to wide color gamut if
-                // the GLES path is still being hit
+                // We don't have an EGL colorspace for extended range P3 that's used for HDR
+                // So override it after configuring the EGL context
                 case ColorMode::Hdr:
                 case ColorMode::Hdr10:
+                    overrideWindowDataSpaceForHdr = true;
+                    attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
+                    break;
                 case ColorMode::WideColorGamut: {
                     skcms_Matrix3x3 colorGamut;
                     LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut),
@@ -491,6 +493,16 @@
                             (void*)window, eglErrorString());
     }
 
+    if (overrideWindowDataSpaceForHdr) {
+        // This relies on knowing that EGL will not re-set the dataspace after the call to
+        // eglCreateWindowSurface. Since the handling of the colorspace extension is largely
+        // implemented in libEGL in the platform, we can safely assume this is the case
+        int32_t err = ANativeWindow_setBuffersDataSpace(
+                window,
+                static_cast<android_dataspace>(STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED));
+        LOG_ALWAYS_FATAL_IF(err, "Failed to ANativeWindow_setBuffersDataSpace %d", err);
+    }
+
     return surface;
 }
 
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 9ebad81..6c2cb9d 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -95,7 +95,6 @@
     virtual void setPictureCapturedCallback(
             const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0;
 
-    virtual bool supportsExtendedRangeHdr() const { return false; }
     virtual void setTargetSdrHdrRatio(float ratio) = 0;
     virtual const SkM44& getPixelSnapMatrix() const = 0;
 
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index 2b7fa04..10f4567 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -453,9 +453,15 @@
     VulkanSurface::NativeBufferInfo* bufferInfo = &mNativeBuffers[idx];
 
     if (bufferInfo->skSurface.get() == nullptr) {
+        SkSurfaceProps surfaceProps;
+        if (mWindowInfo.colorMode != ColorMode::Default) {
+            surfaceProps = SkSurfaceProps(SkSurfaceProps::kAlwaysDither_Flag | surfaceProps.flags(),
+                                          surfaceProps.pixelGeometry());
+        }
         bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer(
                 mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()),
-                kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, nullptr, /*from_window=*/true);
+                kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, &surfaceProps,
+                /*from_window=*/true);
         if (bufferInfo->skSurface.get() == nullptr) {
             ALOGE("SkSurface::MakeFromAHardwareBuffer failed");
             mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer,
@@ -545,16 +551,6 @@
     }
 }
 
-bool VulkanSurface::isBeyond8Bit() const {
-    switch (mWindowInfo.bufferFormat) {
-        case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
-        case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
-            return true;
-        default:
-            return false;
-    }
-}
-
 } /* namespace renderthread */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/renderthread/VulkanSurface.h b/libs/hwui/renderthread/VulkanSurface.h
index d3266af8..6f528010 100644
--- a/libs/hwui/renderthread/VulkanSurface.h
+++ b/libs/hwui/renderthread/VulkanSurface.h
@@ -49,8 +49,6 @@
     void setColorSpace(sk_sp<SkColorSpace> colorSpace);
     const SkM44& getPixelSnapMatrix() const { return mWindowInfo.pixelSnapMatrix; }
 
-    bool isBeyond8Bit() const;
-
 private:
     /*
      * All structs/methods in this private section are specifically for use by the VulkanManager
diff --git a/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
new file mode 100644
index 0000000..80bc5c0
--- /dev/null
+++ b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
@@ -0,0 +1,630 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.soundtrigger;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.hardware.soundtrigger.ConversionUtil;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.media.soundtrigger_middleware.IAcknowledgeEvent;
+import android.media.soundtrigger_middleware.IInjectGlobalEvent;
+import android.media.soundtrigger_middleware.IInjectModelEvent;
+import android.media.soundtrigger_middleware.IInjectRecognitionEvent;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.ISoundTriggerService;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Used to inject/observe events when using a fake SoundTrigger HAL for test purposes.
+ * Created by {@link SoundTriggerManager#getInjection(Executor, GlobalCallback)}.
+ * Only one instance of this class is valid at any given time, old instances will be delivered
+ * {@link GlobalCallback#onPreempted()}.
+ * @hide
+ */
+@TestApi
+public final class SoundTriggerInstrumentation {
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private IInjectGlobalEvent mInjectGlobalEvent = null;
+
+    @GuardedBy("mLock")
+    private Map<IBinder, ModelSession> mModelSessionMap = new HashMap<>();
+    @GuardedBy("mLock")
+    private Map<IBinder, RecognitionSession> mRecognitionSessionMap = new HashMap<>();
+    @GuardedBy("mLock")
+    private IBinder mClientToken = null;
+
+    private final GlobalCallback mClientCallback;
+    private final Executor mGlobalCallbackExecutor;
+
+    /**
+     * Callback interface for un-sessioned events observed from the fake STHAL.
+     * Registered upon construction of {@link SoundTriggerInstrumentation}
+     * @hide
+     */
+    @TestApi
+    public interface GlobalCallback {
+        /**
+         * Called when the created {@link SoundTriggerInstrumentation} object is invalidated
+         * by another client creating an {@link SoundTriggerInstrumentation} to instrument the
+         * fake STHAL. Only one client may inject at a time.
+         * All sessions are invalidated, no further events will be received, and no
+         * injected events will be delivered.
+         */
+        default void onPreempted() {}
+        /**
+         * Called when the STHAL has been restarted by the framework, due to unexpected
+         * error conditions.
+         * Not called when {@link SoundTriggerInstrumentation#triggerRestart()} is injected.
+         */
+        default void onRestarted() {}
+        /**
+         * Called when the framework detaches from the fake HAL.
+         * This is not transmitted to real HALs, but it indicates that the
+         * framework has flushed its global state.
+         */
+        default void onFrameworkDetached() {}
+        /**
+         * Called when a client application attaches to the framework.
+         * This is not transmitted to real HALs, but it represents the state of
+         * the framework.
+         */
+        default void onClientAttached() {}
+        /**
+         * Called when a client application detaches from the framework.
+         * This is not transmitted to real HALs, but it represents the state of
+         * the framework.
+         */
+        default void onClientDetached() {}
+        /**
+         * Called when the fake HAL receives a model load from the framework.
+         * @param modelSession - A session which exposes additional injection
+         *                       functionality associated with the newly loaded
+         *                       model. See {@link ModelSession}.
+         */
+        void onModelLoaded(@NonNull ModelSession modelSession);
+    }
+
+    /**
+     * Callback for HAL events related to a loaded model. Register with
+     * {@link ModelSession#setModelCallback(Executor, ModelCallback)}
+     * Note, callbacks will not be delivered for events triggered by the injection.
+     * @hide
+     */
+    @TestApi
+    public interface ModelCallback {
+        /**
+         * Called when the model associated with the {@link ModelSession} this callback
+         * was registered for was unloaded by the framework.
+         */
+        default void onModelUnloaded() {}
+        /**
+         * Called when the model associated with the {@link ModelSession} this callback
+         * was registered for receives a set parameter call from the framework.
+         * @param param - Parameter being set.
+         *                 See {@link SoundTrigger.ModelParamTypes}
+         * @param value - Value the model parameter was set to.
+         */
+        default void onParamSet(@SoundTrigger.ModelParamTypes int param, int value) {}
+        /**
+         * Called when the model associated with the {@link ModelSession} this callback
+         * was registered for receives a recognition start request.
+         * @param recognitionSession - A session which exposes additional injection
+         *                             functionality associated with the newly started
+         *                             recognition. See {@link RecognitionSession}
+         */
+        void onRecognitionStarted(@NonNull RecognitionSession recognitionSession);
+    }
+
+    /**
+     * Callback for HAL events related to a started recognition. Register with
+     * {@link RecognitionSession#setRecognitionCallback(Executor, RecognitionCallback)}
+     * Note, callbacks will not be delivered for events triggered by the injection.
+     * @hide
+     */
+    @TestApi
+    public interface RecognitionCallback {
+        /**
+         * Called when the recognition associated with the {@link RecognitionSession} this
+         * callback was registered for was stopped by the framework.
+         */
+        void onRecognitionStopped();
+    }
+
+    /**
+     * Session associated with a loaded model in the fake STHAL.
+     * Can be used to query details about the loaded model, register a callback for future
+     * model events, or trigger HAL events associated with a loaded model.
+     * This session is invalid once the model is unloaded, caused by a
+     * {@link ModelSession#triggerUnloadModel()},
+     * the client unloading recognition, or if a {@link GlobalCallback#onRestarted()} is
+     * received.
+     * Further injections on an invalidated session will not be respected, and no future
+     * callbacks will be delivered.
+     * @hide
+     */
+    @TestApi
+    public class ModelSession {
+
+        /**
+         * Trigger the HAL to preemptively unload the model associated with this session.
+         * Typically occurs when a higher priority model is loaded which utilizes the same
+         * resources.
+         */
+        public void triggerUnloadModel() {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                try {
+                    mInjectModelEvent.triggerUnloadModel();
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+                mModelSessionMap.remove(mInjectModelEvent.asBinder());
+            }
+        }
+
+        /**
+         * Get the {@link SoundTriggerManager.Model} associated with this session.
+         * @return - The model associated with this session.
+         */
+        public @NonNull SoundTriggerManager.Model getSoundModel() {
+            return mModel;
+        }
+
+        /**
+         * Get the list of {@link SoundTrigger.Keyphrase} associated with this session.
+         * @return - The keyphrases associated with this session.
+         */
+        public @NonNull List<SoundTrigger.Keyphrase> getPhrases() {
+            if (mPhrases == null) {
+                return new ArrayList<>();
+            } else {
+                return new ArrayList<>(Arrays.asList(mPhrases));
+            }
+        }
+
+        /**
+         * Get whether this model is of keyphrase type.
+         * @return - true if the model is a keyphrase model, false otherwise
+         */
+        public boolean isKeyphrase() {
+            return (mPhrases != null);
+        }
+
+        /**
+         * Registers the model callback associated with this session. Events associated
+         * with this model session will be reported via this callback.
+         * See {@link ModelCallback}
+         * @param executor - Executor which the callback is dispatched on
+         * @param callback - Model callback for reporting model session events.
+         */
+        public void setModelCallback(@NonNull @CallbackExecutor Executor executor, @NonNull
+                ModelCallback callback) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mModelCallback = Objects.requireNonNull(callback);
+                mModelExecutor = Objects.requireNonNull(executor);
+            }
+        }
+
+        /**
+         * Clear the model callback associated with this session, if any has been
+         * set by {@link #setModelCallback(Executor, ModelCallback)}.
+         */
+        public void clearModelCallback() {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mModelCallback = null;
+                mModelExecutor = null;
+            }
+        }
+
+        private ModelSession(SoundModel model, Phrase[] phrases,
+                IInjectModelEvent injection) {
+            mModel = SoundTriggerManager.Model.create(UUID.fromString(model.uuid),
+                    UUID.fromString(model.vendorUuid),
+                    ConversionUtil.sharedMemoryToByteArray(model.data, model.dataSize));
+            if (phrases != null) {
+                mPhrases = new SoundTrigger.Keyphrase[phrases.length];
+                int i = 0;
+                for (var phrase : phrases) {
+                    mPhrases[i++] = ConversionUtil.aidl2apiPhrase(phrase);
+                }
+            } else {
+                mPhrases = null;
+            }
+            mInjectModelEvent = injection;
+        }
+
+        private void wrap(Consumer<ModelCallback> consumer) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (mModelCallback != null && mModelExecutor != null) {
+                    final ModelCallback callback = mModelCallback;
+                    mModelExecutor.execute(() -> consumer.accept(callback));
+                }
+            }
+        }
+
+        private final SoundTriggerManager.Model mModel;
+        private final SoundTrigger.Keyphrase[] mPhrases;
+        private final IInjectModelEvent mInjectModelEvent;
+
+        @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+        private ModelCallback mModelCallback = null;
+        @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+        private Executor mModelExecutor = null;
+    }
+
+    /**
+     * Session associated with a recognition start in the fake STHAL.
+     * Can be used to get information about the started recognition, register a callback
+     * for future events associated with this recognition, and triggering
+     * recognition events or aborts.
+     * This session is invalid once the recognition is stopped, caused by a
+     * {@link RecognitionSession#triggerAbortRecognition()},
+     * {@link RecognitionSession#triggerRecognitionEvent(byte[], List)},
+     * the client stopping recognition, or any operation which invalidates the
+     * {@link ModelSession} which the session was created from.
+     * Further injections on an invalidated session will not be respected, and no future
+     * callbacks will be delivered.
+     * @hide
+     */
+    @TestApi
+    public class RecognitionSession {
+
+        /**
+         * Get an integer token representing the audio session associated with this
+         * recognition in the STHAL.
+         * @return - The session token.
+         */
+        public int getAudioSession() {
+            return mAudioSession;
+        }
+
+        /**
+         * Get the recognition config used to start this recognition.
+         * @return - The config passed to the HAL for startRecognition.
+         */
+        public @NonNull SoundTrigger.RecognitionConfig getRecognitionConfig() {
+            return mRecognitionConfig;
+        }
+
+        /**
+         * Trigger a recognition in the fake STHAL.
+         * @param data - The opaque data buffer included in the recognition event.
+         * @param phraseExtras - Keyphrase metadata included in the event. The
+         *                       event must include metadata for the keyphrase id
+         *                       associated with this model to be received by the
+         *                       client application.
+         */
+        public void triggerRecognitionEvent(@NonNull byte[] data, @Nullable
+                List<SoundTrigger.KeyphraseRecognitionExtra> phraseExtras) {
+            PhraseRecognitionExtra[] converted = null;
+            if (phraseExtras != null) {
+                converted = new PhraseRecognitionExtra[phraseExtras.size()];
+                int i = 0;
+                for (var phraseExtra : phraseExtras) {
+                    converted[i++] = ConversionUtil.api2aidlPhraseRecognitionExtra(phraseExtra);
+                }
+            }
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mRecognitionSessionMap.remove(mInjectRecognitionEvent.asBinder());
+                try {
+                    mInjectRecognitionEvent.triggerRecognitionEvent(data, converted);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+        /**
+         * Trigger an abort recognition event in the fake HAL. This represents a
+         * preemptive ending of the recognition session by the HAL, despite no
+         * recognition detection. Typically occurs during contention for microphone
+         * usage, or if model limits are hit.
+         * See {@link SoundTriggerInstrumentation#setResourceContention(boolean)} to block
+         * subsequent downward calls for contention reasons.
+         */
+        public void triggerAbortRecognition() {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mRecognitionSessionMap.remove(mInjectRecognitionEvent.asBinder());
+                try {
+                    mInjectRecognitionEvent.triggerAbortRecognition();
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+         /**
+         * Registers the recognition callback associated with this session. Events associated
+         * with this recognition session will be reported via this callback.
+         * See {@link RecognitionCallback}
+         * @param executor - Executor which the callback is dispatched on
+         * @param callback - Recognition callback for reporting recognition session events.
+         */
+        public void setRecognitionCallback(@NonNull @CallbackExecutor Executor executor,
+                @NonNull RecognitionCallback callback) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mRecognitionCallback = callback;
+                mRecognitionExecutor = executor;
+            }
+        }
+
+        /**
+         * Clear the recognition callback associated with this session, if any has been
+         * set by {@link #setRecognitionCallback(Executor, RecognitionCallback)}.
+         */
+        public void clearRecognitionCallback() {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mRecognitionCallback = null;
+                mRecognitionExecutor = null;
+            }
+        }
+
+        private RecognitionSession(int audioSession,
+                RecognitionConfig recognitionConfig,
+                IInjectRecognitionEvent injectRecognitionEvent) {
+            mAudioSession = audioSession;
+            mRecognitionConfig = ConversionUtil.aidl2apiRecognitionConfig(recognitionConfig);
+            mInjectRecognitionEvent = injectRecognitionEvent;
+        }
+
+        private void wrap(Consumer<RecognitionCallback> consumer) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (mRecognitionCallback != null && mRecognitionExecutor != null) {
+                    final RecognitionCallback callback = mRecognitionCallback;
+                    mRecognitionExecutor.execute(() -> consumer.accept(callback));
+                }
+            }
+        }
+
+        private final int mAudioSession;
+        private final SoundTrigger.RecognitionConfig mRecognitionConfig;
+        private final IInjectRecognitionEvent mInjectRecognitionEvent;
+
+        @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+        private Executor mRecognitionExecutor = null;
+        @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+        private RecognitionCallback mRecognitionCallback = null;
+    }
+
+    // Implementation of injection interface passed to the HAL.
+    // This class will re-associate events received on this callback interface
+    // with sessions, to avoid staleness issues.
+    private class Injection extends ISoundTriggerInjection.Stub {
+        @Override
+        public void registerGlobalEventInjection(IInjectGlobalEvent globalInjection) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mInjectGlobalEvent = globalInjection;
+            }
+        }
+
+        @Override
+        public void onSoundModelLoaded(SoundModel model, @Nullable Phrase[] phrases,
+                            IInjectModelEvent modelInjection, IInjectGlobalEvent globalSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+                ModelSession modelSession = new ModelSession(model, phrases, modelInjection);
+                mModelSessionMap.put(modelInjection.asBinder(), modelSession);
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onModelLoaded(modelSession));
+            }
+        }
+
+        @Override
+        public void onSoundModelUnloaded(IInjectModelEvent modelSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                ModelSession clientModelSession = mModelSessionMap.remove(modelSession.asBinder());
+                if (clientModelSession == null) return;
+                clientModelSession.wrap((ModelCallback cb) -> cb.onModelUnloaded());
+            }
+        }
+
+        @Override
+        public void onRecognitionStarted(int audioSessionHandle, RecognitionConfig config,
+                IInjectRecognitionEvent recognitionInjection, IInjectModelEvent modelSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                ModelSession clientModelSession = mModelSessionMap.get(modelSession.asBinder());
+                if (clientModelSession == null) return;
+                RecognitionSession recogSession = new RecognitionSession(
+                        audioSessionHandle, config, recognitionInjection);
+                mRecognitionSessionMap.put(recognitionInjection.asBinder(), recogSession);
+                clientModelSession.wrap((ModelCallback cb) ->
+                        cb.onRecognitionStarted(recogSession));
+            }
+        }
+
+        @Override
+        public void onRecognitionStopped(IInjectRecognitionEvent recognitionSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                RecognitionSession clientRecognitionSession =
+                        mRecognitionSessionMap.remove(recognitionSession.asBinder());
+                if (clientRecognitionSession == null) return;
+                clientRecognitionSession.wrap((RecognitionCallback cb)
+                        -> cb.onRecognitionStopped());
+            }
+        }
+
+        @Override
+        public void onParamSet(int modelParam, int value, IInjectModelEvent modelSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                ModelSession clientModelSession = mModelSessionMap.get(modelSession.asBinder());
+                if (clientModelSession == null) return;
+                clientModelSession.wrap((ModelCallback cb) -> cb.onParamSet(modelParam, value));
+            }
+        }
+
+
+        @Override
+        public void onRestarted(IInjectGlobalEvent globalSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+                mRecognitionSessionMap.clear();
+                mModelSessionMap.clear();
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onRestarted());
+            }
+        }
+
+        @Override
+        public void onFrameworkDetached(IInjectGlobalEvent globalSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onFrameworkDetached());
+            }
+        }
+
+        @Override
+        public void onClientAttached(IBinder token, IInjectGlobalEvent globalSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+                mClientToken = token;
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onClientAttached());
+            }
+        }
+
+        @Override
+        public void onClientDetached(IBinder token) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (token != mClientToken) return;
+                mClientToken = null;
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onClientDetached());
+            }
+        }
+
+        @Override
+        public void onPreempted() {
+            // This is always valid, independent of session
+            mGlobalCallbackExecutor.execute(() -> mClientCallback.onPreempted());
+            // Callbacks will no longer be delivered, and injection will be silently dropped.
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    public SoundTriggerInstrumentation(ISoundTriggerService service,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull GlobalCallback callback) {
+        mClientCallback = Objects.requireNonNull(callback);
+        mGlobalCallbackExecutor = Objects.requireNonNull(executor);
+        try {
+            service.attachInjection(new Injection());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Simulate a HAL restart, typically caused by the framework on an unexpected error,
+     * or a restart of the core audio HAL.
+     * Application sessions will be detached, and all state will be cleared. The framework
+     * will re-attach to the HAL following restart.
+     * @hide
+     */
+    @TestApi
+    public void triggerRestart() {
+        synchronized (mLock) {
+            if (mInjectGlobalEvent == null) {
+                throw new IllegalStateException(
+                        "Attempted to trigger HAL restart before registration");
+            }
+            try {
+                mInjectGlobalEvent.triggerRestart();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Trigger a resource available callback from the fake SoundTrigger HAL to the framework.
+     * This callback notifies the framework that methods which previously failed due to
+     * resource contention may now succeed.
+     * @hide
+     */
+    @TestApi
+    public void triggerOnResourcesAvailable() {
+        synchronized (mLock) {
+            if (mInjectGlobalEvent == null) {
+                throw new IllegalStateException(
+                        "Attempted to trigger HAL resources available before registration");
+            }
+            try {
+                mInjectGlobalEvent.triggerOnResourcesAvailable();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Simulate resource contention, similar to when HAL which does not
+     * support concurrent capture opens a capture stream, or when a HAL
+     * has reached its maximum number of models.
+     * Subsequent model loads and recognition starts will gracefully error.
+     * Since this call does not trigger a callback through the framework, the
+     * call will block until the fake HAL has acknowledged the state change.
+     * @param isResourceContended - true to enable contention, false to return
+     *                              to normal functioning.
+     * @hide
+     */
+    @TestApi
+    public void setResourceContention(boolean isResourceContended) {
+        synchronized (mLock) {
+            if (mInjectGlobalEvent == null) {
+                throw new IllegalStateException("Injection interface not set up");
+            }
+            IInjectGlobalEvent current = mInjectGlobalEvent;
+            final CountDownLatch signal = new CountDownLatch(1);
+            try {
+                current.setResourceContention(isResourceContended, new IAcknowledgeEvent.Stub() {
+                    @Override
+                    public void eventReceived() {
+                        signal.countDown();
+                    }
+                });
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+
+            // Block until we get a callback from the service that our request was serviced.
+            try {
+                // Rely on test timeout if we don't get a response.
+                signal.await();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
+
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index ae8121a..c41bd1b 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -18,11 +18,13 @@
 
 import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
@@ -45,6 +47,7 @@
 import android.os.IBinder;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.provider.Settings;
 import android.util.Slog;
 
@@ -53,9 +56,9 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.HashMap;
-import java.util.List;
 import java.util.Objects;
 import java.util.UUID;
+import java.util.concurrent.Executor;
 
 /**
  * This class provides management of non-voice (general sound trigger) based sound recognition
@@ -609,4 +612,24 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Create a {@link SoundTriggerInstrumentation} for test purposes, which instruments a fake
+     * STHAL. Clients must attach to the appropriate underlying ST module.
+     * @param executor - Executor to dispatch global callbacks on
+     * @param callback - Callback for unsessioned events received by the fake STHAL
+     * @return - A {@link SoundTriggerInstrumentation} for observation/injection.
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    @NonNull
+    public static SoundTriggerInstrumentation attachInstrumentation(
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull SoundTriggerInstrumentation.GlobalCallback callback) {
+        ISoundTriggerService service = ISoundTriggerService.Stub.asInterface(
+                    ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE));
+        return new SoundTriggerInstrumentation(service, executor, callback);
+    }
+
 }
diff --git a/media/java/android/media/voice/KeyphraseModelManager.java b/media/java/android/media/voice/KeyphraseModelManager.java
index 8ec8967..5a690a5 100644
--- a/media/java/android/media/voice/KeyphraseModelManager.java
+++ b/media/java/android/media/voice/KeyphraseModelManager.java
@@ -21,7 +21,9 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.Slog;
@@ -154,4 +156,23 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Override the persistent enrolled model database with an in-memory
+     * fake for testing purposes.
+     *
+     * @param enabled - {@code true} if the model enrollment database should be overridden with an
+     * in-memory fake. {@code false} if the real, persistent model enrollment database should be
+     * used.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+    @TestApi
+    public void setModelDatabaseForTestEnabled(boolean enabled) {
+        try {
+            mVoiceInteractionManagerService.setModelDatabaseForTestEnabled(enabled, new Binder());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/media/jni/android_media_MediaCodecLinearBlock.h b/media/jni/android_media_MediaCodecLinearBlock.h
index c753020..060abfd 100644
--- a/media/jni/android_media_MediaCodecLinearBlock.h
+++ b/media/jni/android_media_MediaCodecLinearBlock.h
@@ -44,12 +44,19 @@
 
     std::shared_ptr<C2Buffer> toC2Buffer(size_t offset, size_t size) const {
         if (mBuffer) {
+            // TODO: if returned C2Buffer is different from mBuffer, we should
+            // find a way to connect the life cycle between this C2Buffer and
+            // mBuffer.
             if (mBuffer->data().type() != C2BufferData::LINEAR) {
                 return nullptr;
             }
             C2ConstLinearBlock block = mBuffer->data().linearBlocks().front();
             if (offset == 0 && size == block.capacity()) {
-                return mBuffer;
+                // Let C2Buffer be new one to queue to MediaCodec. It will allow
+                // the related input slot to be released by onWorkDone from C2
+                // Component. Currently, the life cycle of mBuffer should be
+                // protected by different flows.
+                return std::make_shared<C2Buffer>(*mBuffer);
             }
 
             std::shared_ptr<C2Buffer> buffer =
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index d87abb9..ebfb86d 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -34,7 +34,7 @@
     <string name="summary_watch">This app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string>
 
     <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile for singleDevice(type) [CHAR LIMIT=NONE] -->
-    <string name="summary_watch_single_device">This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="device_name" example="phone">%1$s</xliff:g></string>
+    <string name="summary_watch_single_device">This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="device_type" example="phone">%1$s</xliff:g></string>
 
     <!-- ================= DEVICE_PROFILE_GLASSES ================= -->
 
@@ -48,7 +48,7 @@
     <string name="summary_glasses_multi_device">This app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
 
     <!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile for singleDevice(type) [CHAR LIMIT=NONE] -->
-    <string name="summary_glasses_single_device">This app will be allowed to access these permissions on your <xliff:g id="device_name" example="phone">%1$s</xliff:g></string>
+    <string name="summary_glasses_single_device">This app will be allowed to access these permissions on your <xliff:g id="device_type" example="phone">%1$s</xliff:g></string>
 
     <!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
 
@@ -59,7 +59,7 @@
     <string name="helper_title_app_streaming">Cross-device services</string>
 
     <!-- Description of the helper dialog for APP_STREAMING profile. [CHAR LIMIT=NONE] -->
-    <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="Chromebook">%2$s</xliff:g> to stream apps between your devices</string>
+    <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to stream apps between your devices</string>
 
     <!-- ================= DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ================= -->
 
@@ -81,7 +81,7 @@
     <string name="helper_title_computer">Google Play services</string>
 
     <!-- Description of the helper dialog for COMPUTER profile. [CHAR LIMIT=NONE] -->
-    <string name="helper_summary_computer"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="Chromebook">%2$s</xliff:g> to access your phone\u2019s photos, media, and notifications</string>
+    <string name="helper_summary_computer"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to access your phone\u2019s photos, media, and notifications</string>
 
     <!-- ================= DEVICE_PROFILE_NEARBY_DEVICE_STREAMING ================= -->
 
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index e9b2e10..3e65251 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -117,6 +117,8 @@
   <string name="get_dialog_sign_in_type_username_separator" translatable="false">" • "</string>
   <!-- This text is followed by a list of one or more options. [CHAR LIMIT=80] -->
   <string name="get_dialog_title_sign_in_options">Sign-in options</string>
+  <!-- Button label for viewing the full information about an account. [CHAR LIMIT=80] -->
+  <string name="button_label_view_more">View more</string>
   <!-- Column heading for displaying sign-ins for a specific username. [CHAR LIMIT=80] -->
   <string name="get_dialog_heading_for_username">For <xliff:g id="username" example="becket@gmail.com">%1$s</xliff:g></string>
   <!-- Column heading for displaying locked (that is, the user needs to first authenticate via pin, fingerprint, faceId, etc.) sign-ins. [CHAR LIMIT=80] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index dd4419b..e53e956 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -65,8 +65,8 @@
         )
 
         val originName: String? = when (requestInfo?.type) {
-            RequestInfo.TYPE_CREATE -> requestInfo?.createCredentialRequest?.origin
-            RequestInfo.TYPE_GET -> requestInfo?.getCredentialRequest?.origin
+            RequestInfo.TYPE_CREATE -> requestInfo.createCredentialRequest?.origin
+            RequestInfo.TYPE_GET -> requestInfo.getCredentialRequest?.origin
             else -> null
         }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 725401f..64addf2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -210,7 +210,11 @@
                 appName = originName
                     ?: getAppLabel(context.packageManager, requestInfo.appPackageName)
                     ?: return null,
-                preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials
+                preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials,
+                preferIdentityDocUi = getCredentialRequest.data.getBoolean(
+                    // TODO(b/276777444): replace with direct library constant reference once
+                    // exposed.
+                    "androidx.credentials.BUNDLE_KEY_PREFER_IDENTITY_DOC_UI"),
             )
         }
 
@@ -241,20 +245,13 @@
                             userName = credentialEntry.username.toString(),
                             displayName = credentialEntry.displayName?.toString(),
                             icon = credentialEntry.icon.loadDrawable(context),
-                            shouldTintIcon = credentialEntry.isDefaultIcon ?: false,
+                            shouldTintIcon = credentialEntry.isDefaultIcon,
                             lastUsedTimeMillis = credentialEntry.lastUsedTime,
                             isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
                                 credentialEntry.autoSelectAllowedFromOption,
                         ))
                     }
                     is PublicKeyCredentialEntry -> {
-                        val passkeyUsername = credentialEntry.username.toString()
-                        val passkeyDisplayName = credentialEntry.displayName?.toString() ?: ""
-                        val (username, displayName) = userAndDisplayNameForPasskey(
-                            passkeyUsername = passkeyUsername,
-                            passkeyDisplayName = passkeyDisplayName,
-                        )
-
                         result.add(CredentialEntryInfo(
                             providerId = providerId,
                             providerDisplayName = providerLabel,
@@ -264,8 +261,8 @@
                             fillInIntent = it.frameworkExtrasIntent,
                             credentialType = CredentialType.PASSKEY,
                             credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
-                            userName = username,
-                            displayName = displayName,
+                            userName = credentialEntry.username.toString(),
+                            displayName = credentialEntry.displayName?.toString(),
                             icon = credentialEntry.icon.loadDrawable(context),
                             shouldTintIcon = credentialEntry.isDefaultIcon,
                             lastUsedTimeMillis = credentialEntry.lastUsedTime,
@@ -703,7 +700,7 @@
  * 2) username on top if display-name is not available.
  * 3) don't show username on second line if username == display-name
  */
-private fun userAndDisplayNameForPasskey(
+fun userAndDisplayNameForPasskey(
     passkeyUsername: String,
     passkeyDisplayName: String,
 ): Pair<String, String> {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
index 307d953..10a75d4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
@@ -316,7 +316,7 @@
         rememberModalBottomSheetState(Hidden),
     sheetShape: Shape = MaterialTheme.shapes.large,
     sheetElevation: Dp = ModalBottomSheetDefaults.Elevation,
-    sheetBackgroundColor: Color = MaterialTheme.colorScheme.surface,
+    sheetBackgroundColor: Color,
     sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
     content: @Composable () -> Unit
 ) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 7a720b1..2dba2ab 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -32,7 +32,6 @@
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.SuggestionChip
 import androidx.compose.material3.SuggestionChipDefaults
 import androidx.compose.material3.TopAppBar
@@ -52,6 +51,7 @@
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
@@ -80,6 +80,7 @@
     /** If true, draws a trailing lock icon. */
     isLockedAuthEntry: Boolean = false,
     enforceOneLine: Boolean = false,
+    onTextLayout: (TextLayoutResult) -> Unit = {},
 ) {
     val iconPadding = Modifier.wrapContentSize().padding(
         // Horizontal padding should be 16dp, but the suggestion chip itself
@@ -104,7 +105,11 @@
             ) {
                 // Apply weight so that the trailing icon can always show.
                 Column(modifier = Modifier.wrapContentHeight().fillMaxWidth().weight(1f)) {
-                    SmallTitleText(text = entryHeadlineText, enforceOneLine = enforceOneLine)
+                    SmallTitleText(
+                        text = entryHeadlineText,
+                        enforceOneLine = enforceOneLine,
+                        onTextLayout = onTextLayout,
+                    )
                     if (passwordValue != null) {
                         Row(
                             modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
@@ -142,10 +147,18 @@
                             )
                         }
                     } else if (entrySecondLineText != null) {
-                        BodySmallText(text = entrySecondLineText, enforceOneLine = enforceOneLine)
+                        BodySmallText(
+                            text = entrySecondLineText,
+                            enforceOneLine = enforceOneLine,
+                            onTextLayout = onTextLayout,
+                        )
                     }
                     if (entryThirdLineText != null) {
-                        BodySmallText(text = entryThirdLineText, enforceOneLine = enforceOneLine)
+                        BodySmallText(
+                            text = entryThirdLineText,
+                            enforceOneLine = enforceOneLine,
+                            onTextLayout = onTextLayout,
+                        )
                     }
                 }
                 if (isLockedAuthEntry) {
@@ -155,7 +168,7 @@
                             // Decorative purpose only.
                             contentDescription = null,
                             modifier = Modifier.size(24.dp),
-                            tint = MaterialTheme.colorScheme.onSurfaceVariant,
+                            tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
                         )
                     }
                 }
@@ -169,7 +182,7 @@
                         Icon(
                             modifier = iconSize,
                             bitmap = iconImageBitmap,
-                            tint = MaterialTheme.colorScheme.onSurfaceVariant,
+                            tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
                             // Decorative purpose only.
                             contentDescription = null,
                         )
@@ -193,7 +206,7 @@
                     Icon(
                         modifier = iconSize,
                         imageVector = iconImageVector,
-                        tint = MaterialTheme.colorScheme.onSurfaceVariant,
+                        tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
                         // Decorative purpose only.
                         contentDescription = null,
                     )
@@ -205,7 +218,7 @@
                     Icon(
                         modifier = iconSize,
                         painter = iconPainter,
-                        tint = MaterialTheme.colorScheme.onSurfaceVariant,
+                        tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
                         // Decorative purpose only.
                         contentDescription = null,
                     )
@@ -217,9 +230,8 @@
         border = null,
         colors = SuggestionChipDefaults.suggestionChipColors(
             containerColor = LocalAndroidColorScheme.current.colorSurfaceContainerHigh,
-            // TODO: remove?
-            labelColor = MaterialTheme.colorScheme.onSurfaceVariant,
-            iconContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+            labelColor = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+            iconContentColor = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
         ),
     )
 }
@@ -282,7 +294,7 @@
         Icon(
             modifier = Modifier.size(24.dp),
             painter = leadingIconPainter,
-            tint = MaterialTheme.colorScheme.onSurfaceVariant,
+            tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
             // Decorative purpose only.
             contentDescription = null,
         )
@@ -341,7 +353,7 @@
                             R.string.accessibility_back_arrow_button
                         ),
                         modifier = Modifier.size(24.dp).autoMirrored(),
-                        tint = MaterialTheme.colorScheme.onSurfaceVariant,
+                        tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
                     )
                 }
             }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
index 3581228..14bf4f2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
@@ -20,20 +20,20 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.wrapContentHeight
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 
 @Composable
 fun CredentialListSectionHeader(text: String) {
-    InternalSectionHeader(text, MaterialTheme.colorScheme.onSurfaceVariant)
+    InternalSectionHeader(text, LocalAndroidColorScheme.current.colorOnSurfaceVariant)
 }
 
 @Composable
 fun MoreAboutPasskeySectionHeader(text: String) {
-    InternalSectionHeader(text, MaterialTheme.colorScheme.onSurface)
+    InternalSectionHeader(text, LocalAndroidColorScheme.current.colorOnSurface)
 }
 
 @Composable
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
index 22871bcb..6b46636 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
@@ -22,8 +22,10 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.text.style.TextOverflow
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 
 /**
  * The headline for a screen. E.g. "Create a passkey for X", "Choose a saved sign-in for X".
@@ -35,7 +37,7 @@
     Text(
         modifier = modifier.wrapContentSize(),
         text = text,
-        color = MaterialTheme.colorScheme.onSurface,
+        color = LocalAndroidColorScheme.current.colorOnSurface,
         textAlign = TextAlign.Center,
         style = MaterialTheme.typography.headlineSmall,
     )
@@ -49,7 +51,7 @@
     Text(
         modifier = modifier.wrapContentSize(),
         text = text,
-        color = MaterialTheme.colorScheme.onSurfaceVariant,
+        color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
         style = MaterialTheme.typography.bodyMedium,
     )
 }
@@ -58,14 +60,20 @@
  * Body-small typography; on-surface-variant color.
  */
 @Composable
-fun BodySmallText(text: String, modifier: Modifier = Modifier, enforceOneLine: Boolean = false) {
+fun BodySmallText(
+    text: String,
+    modifier: Modifier = Modifier,
+    enforceOneLine: Boolean = false,
+    onTextLayout: (TextLayoutResult) -> Unit = {},
+) {
     Text(
         modifier = modifier.wrapContentSize(),
         text = text,
-        color = MaterialTheme.colorScheme.onSurfaceVariant,
+        color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
         style = MaterialTheme.typography.bodySmall,
         overflow = TextOverflow.Ellipsis,
-        maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE
+        maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE,
+        onTextLayout = onTextLayout,
     )
 }
 
@@ -77,7 +85,7 @@
     Text(
         modifier = modifier.wrapContentSize(),
         text = text,
-        color = MaterialTheme.colorScheme.onSurface,
+        color = LocalAndroidColorScheme.current.colorOnSurface,
         style = MaterialTheme.typography.titleLarge,
     )
 }
@@ -86,14 +94,20 @@
  * Title-small typography; on-surface color.
  */
 @Composable
-fun SmallTitleText(text: String, modifier: Modifier = Modifier, enforceOneLine: Boolean = false) {
+fun SmallTitleText(
+    text: String,
+    modifier: Modifier = Modifier,
+    enforceOneLine: Boolean = false,
+    onTextLayout: (TextLayoutResult) -> Unit = {},
+) {
     Text(
         modifier = modifier.wrapContentSize(),
         text = text,
-        color = MaterialTheme.colorScheme.onSurface,
+        color = LocalAndroidColorScheme.current.colorOnSurface,
         style = MaterialTheme.typography.titleSmall,
         overflow = TextOverflow.Ellipsis,
-        maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE
+        maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE,
+        onTextLayout = onTextLayout,
     )
 }
 
@@ -145,7 +159,7 @@
         modifier = modifier.wrapContentSize(),
         text = text,
         textAlign = TextAlign.Center,
-        color = MaterialTheme.colorScheme.onSurfaceVariant,
+        color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
         style = MaterialTheme.typography.labelLarge,
     )
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 648d832..66d7db8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -30,7 +30,6 @@
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.material3.Divider
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.NewReleases
 import androidx.compose.material.icons.filled.Add
@@ -67,6 +66,7 @@
 import com.android.credentialmanager.common.ui.PasskeyBenefitRow
 import com.android.credentialmanager.common.ui.HeadlineText
 import com.android.credentialmanager.logging.CreateCredentialEvent
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 import com.android.internal.logging.UiEventLogger.UiEventEnum
 
 @Composable
@@ -559,7 +559,7 @@
             item {
                 Divider(
                     thickness = 1.dp,
-                    color = MaterialTheme.colorScheme.outlineVariant,
+                    color = LocalAndroidColorScheme.current.colorOutlineVariant,
                     modifier = Modifier.padding(vertical = 16.dp)
                 )
             }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 6d7ecd7..b8b7ac3 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -34,11 +34,15 @@
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.asImageBitmap
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.unit.dp
 import androidx.core.graphics.drawable.toBitmap
 import com.android.credentialmanager.CredentialSelectorViewModel
@@ -62,6 +66,7 @@
 import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
 import com.android.credentialmanager.common.ui.Snackbar
 import com.android.credentialmanager.logging.GetCredentialEvent
+import com.android.credentialmanager.userAndDisplayNameForPasskey
 import com.android.internal.logging.UiEventLogger.UiEventEnum
 
 @Composable
@@ -158,6 +163,7 @@
     onMoreOptionSelected: () -> Unit,
     onLog: @Composable (UiEventEnum) -> Unit,
 ) {
+    val showMoreForTruncatedEntry = remember { mutableStateOf(false) }
     val sortedUserNameToCredentialEntryList =
         providerDisplayInfo.sortedUserNameToCredentialEntryList
     val authenticationEntryList = providerDisplayInfo.authenticationEntryList
@@ -209,6 +215,8 @@
                 Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
                     val usernameForCredentialSize = sortedUserNameToCredentialEntryList.size
                     val authenticationEntrySize = authenticationEntryList.size
+                    // If true, render a view more button for the single truncated entry on the
+                    // front page.
                     // Show max 4 entries in this primary page
                     if (usernameForCredentialSize + authenticationEntrySize <= 4) {
                         sortedUserNameToCredentialEntryList.forEach {
@@ -216,6 +224,9 @@
                                 credentialEntryInfo = it.sortedCredentialEntryList.first(),
                                 onEntrySelected = onEntrySelected,
                                 enforceOneLine = true,
+                                onTextLayout = {
+                                    showMoreForTruncatedEntry.value = it.hasVisualOverflow
+                                }
                             )
                         }
                         authenticationEntryList.forEach {
@@ -269,6 +280,13 @@
                             onMoreOptionSelected
                         )
                     }
+                } else if (showMoreForTruncatedEntry.value) {
+                    {
+                        ActionButton(
+                            stringResource(R.string.button_label_view_more),
+                            onMoreOptionSelected
+                        )
+                    }
                 } else null,
                 rightButton = if (activeEntry != null) { // Only one sign-in options exist
                     {
@@ -438,7 +456,12 @@
     credentialEntryInfo: CredentialEntryInfo,
     onEntrySelected: (BaseEntry) -> Unit,
     enforceOneLine: Boolean = false,
+    onTextLayout: (TextLayoutResult) -> Unit = {},
 ) {
+    val (username, displayName) = if (credentialEntryInfo.credentialType == CredentialType.PASSKEY)
+        userAndDisplayNameForPasskey(
+            credentialEntryInfo.userName, credentialEntryInfo.displayName ?: "")
+    else Pair(credentialEntryInfo.userName, credentialEntryInfo.displayName)
     Entry(
         onClick = { onEntrySelected(credentialEntryInfo) },
         iconImageBitmap = credentialEntryInfo.icon?.toBitmap()?.asImageBitmap(),
@@ -447,13 +470,13 @@
         iconPainter =
         if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24)
         else null,
-        entryHeadlineText = credentialEntryInfo.userName,
+        entryHeadlineText = username,
         entrySecondLineText = if (
             credentialEntryInfo.credentialType == CredentialType.PASSWORD) {
             "••••••••••••"
         } else {
             val itemsToDisplay = listOf(
-                credentialEntryInfo.displayName,
+                displayName,
                 credentialEntryInfo.credentialTypeDisplayName,
                 credentialEntryInfo.providerDisplayName
             ).filterNot(TextUtils::isEmpty)
@@ -463,6 +486,7 @@
             )
         },
         enforceOneLine = enforceOneLine,
+        onTextLayout = onTextLayout,
     )
 }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 7a86790..c9c62a4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -42,7 +42,7 @@
 }
 
 internal fun isFallbackScreen(state: GetCredentialUiState): Boolean {
-    return false
+    return state.requestDisplayInfo.preferIdentityDocUi
 }
 
 internal fun findAutoSelectEntry(providerDisplayInfo: ProviderDisplayInfo): CredentialEntryInfo? {
@@ -172,6 +172,7 @@
 data class RequestDisplayInfo(
     val appName: String,
     val preferImmediatelyAvailableCredentials: Boolean,
+    val preferIdentityDocUi: Boolean,
 )
 
 /**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
index 8928e18..a33904d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
@@ -42,6 +42,9 @@
 class AndroidColorScheme internal constructor(context: Context) {
     val colorSurfaceBright = getColor(context, R.attr.materialColorSurfaceBright)
     val colorSurfaceContainerHigh = getColor(context, R.attr.materialColorSurfaceContainerHigh)
+    val colorOutlineVariant = getColor(context, R.attr.materialColorOutlineVariant)
+    val colorOnSurface = getColor(context, R.attr.materialColorOnSurface)
+    val colorOnSurfaceVariant = getColor(context, R.attr.materialColorOnSurfaceVariant)
 
     companion object {
         fun getColor(context: Context, attr: Int): Color {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index 47ac2df..e07a629 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -69,6 +69,7 @@
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
@@ -460,6 +461,9 @@
                 ProvideTextStyle(value = titleTextStyle) {
                     CompositionLocalProvider(
                         LocalContentColor provides titleContentColor,
+                        // Disable the title font scaling by only passing the density but not the
+                        // font scale.
+                        LocalDensity provides Density(density = LocalDensity.current.density),
                         content = title
                     )
                 }
diff --git a/packages/SettingsLib/res/drawable/ic_dock_device.xml b/packages/SettingsLib/res/drawable/ic_dock_device.xml
new file mode 100644
index 0000000..96a4900
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_dock_device.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="960"
+        android:viewportHeight="960"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M480,280Q497,280 508.5,268.5Q520,257 520,240Q520,223 508.5,211.5Q497,200 480,200Q463,200 451.5,211.5Q440,223 440,240Q440,257 451.5,268.5Q463,280 480,280ZM120,720Q87,720 63.5,696.5Q40,673 40,640L60,160Q60,127 83.5,103.5Q107,80 140,80L820,80Q853,80 876.5,103.5Q900,127 900,160L920,640Q920,673 896.5,696.5Q873,720 840,720L120,720ZM120,640L840,640Q840,640 840,640Q840,640 840,640L820,160Q820,160 820,160Q820,160 820,160L140,160Q140,160 140,160Q140,160 140,160L120,640Q120,640 120,640Q120,640 120,640ZM320,880Q259,880 209.5,850Q160,820 160,765L160,720L240,720L240,760Q253,780 274.5,790Q296,800 320,800L640,800Q664,800 685.5,790.5Q707,781 720,761L720,720L800,720L800,765Q800,820 750.5,850Q701,880 640,880L320,880ZM480,400Q480,400 480,400Q480,400 480,400L480,400Q480,400 480,400Q480,400 480,400L480,400Q480,400 480,400Q480,400 480,400L480,400Q480,400 480,400Q480,400 480,400L480,400Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverLogging.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverLogging.java
new file mode 100644
index 0000000..5326e73
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverLogging.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.fuelgauge;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Utilities related to battery saver logging.
+ */
+public final class BatterySaverLogging {
+    /**
+     * Record the reason while enabling power save mode manually.
+     * See {@link SaverManualEnabledReason} for all available states.
+     */
+    public static final String EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON =
+            "extra_power_save_mode_manual_enabled_reason";
+
+    /** Broadcast action to record battery saver manual enabled reason. */
+    public static final String ACTION_SAVER_MANUAL_ENABLED_REASON =
+            "com.android.settingslib.fuelgauge.ACTION_SAVER_MANUAL_ENABLED_REASON";
+
+    /** An interface for the battery saver manual enable reason. */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({SAVER_ENABLED_UNKNOWN, SAVER_ENABLED_CONFIRMATION, SAVER_ENABLED_VOICE,
+            SAVER_ENABLED_SETTINGS, SAVER_ENABLED_QS, SAVER_ENABLED_LOW_WARNING,
+            SAVER_ENABLED_SEVERE_WARNING})
+    public @interface SaverManualEnabledReason {}
+
+    public static final int SAVER_ENABLED_UNKNOWN = 0;
+    public static final int SAVER_ENABLED_CONFIRMATION = 1;
+    public static final int SAVER_ENABLED_VOICE = 2;
+    public static final int SAVER_ENABLED_SETTINGS = 3;
+    public static final int SAVER_ENABLED_QS = 4;
+    public static final int SAVER_ENABLED_LOW_WARNING = 5;
+    public static final int SAVER_ENABLED_SEVERE_WARNING = 6;
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index 52f3111..e28ada4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -16,6 +16,10 @@
 
 package com.android.settingslib.fuelgauge;
 
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.ACTION_SAVER_MANUAL_ENABLED_REASON;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SaverManualEnabledReason;
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -116,9 +120,9 @@
      * @return true if the request succeeded.
      */
     public static synchronized boolean setPowerSaveMode(Context context,
-            boolean enable, boolean needFirstTimeWarning) {
+            boolean enable, boolean needFirstTimeWarning, @SaverManualEnabledReason int reason) {
         if (DEBUG) {
-            Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF"));
+            Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF") + ", reason: " + reason);
         }
         final ContentResolver cr = context.getContentResolver();
 
@@ -145,8 +149,10 @@
                         && Global.getInt(cr, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0) == 0
                         && Secure.getInt(cr,
                         Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, 0) == 0) {
-                    showAutoBatterySaverSuggestion(context, confirmationExtras);
+                    sendSystemUiBroadcast(context, ACTION_SHOW_AUTO_SAVER_SUGGESTION,
+                            confirmationExtras);
                 }
+                recordBatterySaverEnabledReason(context, reason);
             }
 
             return true;
@@ -175,21 +181,23 @@
             // Already shown.
             return false;
         }
-        context.sendBroadcast(
-                getSystemUiBroadcast(ACTION_SHOW_START_SAVER_CONFIRMATION, extras));
+        sendSystemUiBroadcast(context, ACTION_SHOW_START_SAVER_CONFIRMATION, extras);
         return true;
     }
 
-    private static void showAutoBatterySaverSuggestion(Context context, Bundle extras) {
-        context.sendBroadcast(getSystemUiBroadcast(ACTION_SHOW_AUTO_SAVER_SUGGESTION, extras));
+    private static void recordBatterySaverEnabledReason(Context context,
+            @SaverManualEnabledReason int reason) {
+        final Bundle enabledReasonExtras = new Bundle(1);
+        enabledReasonExtras.putInt(EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON, reason);
+        sendSystemUiBroadcast(context, ACTION_SAVER_MANUAL_ENABLED_REASON, enabledReasonExtras);
     }
 
-    private static Intent getSystemUiBroadcast(String action, Bundle extras) {
-        final Intent i = new Intent(action);
-        i.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        i.setPackage(SYSUI_PACKAGE);
-        i.putExtras(extras);
-        return i;
+    private static void sendSystemUiBroadcast(Context context, String action, Bundle extras) {
+        final Intent intent = new Intent(action);
+        intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.setPackage(SYSUI_PACKAGE);
+        intent.putExtras(extras);
+        context.sendBroadcast(intent);
     }
 
     private static void setBatterySaverConfirmationAcknowledged(Context context) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
index 6c0eab3..e38e041 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
@@ -55,7 +55,7 @@
                 new Device(
                     AudioDeviceInfo.TYPE_DOCK,
                     MediaRoute2Info.TYPE_DOCK,
-                    R.drawable.ic_headphone),
+                    R.drawable.ic_dock_device),
                 new Device(
                     AudioDeviceInfo.TYPE_HDMI,
                     MediaRoute2Info.TYPE_HDMI,
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
index ad022a6..cb386fb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.fuelgauge;
 
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_UNKNOWN;
 import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_NO_SCHEDULE;
 import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_PERCENTAGE;
 
@@ -72,7 +73,8 @@
         Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isFalse();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+                SAVER_ENABLED_UNKNOWN)).isFalse();
 
         verify(mMockContext, times(1)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(0)).setPowerSaveModeEnabled(anyBoolean());
@@ -92,7 +94,8 @@
         Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -111,7 +114,8 @@
         Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
         Secure.putInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 1);
 
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -129,7 +133,8 @@
         Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -147,7 +152,8 @@
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
         // When disabling, needFirstTimeWarning doesn't matter.
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false));
@@ -166,7 +172,8 @@
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
         // When disabling, needFirstTimeWarning doesn't matter.
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false));
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 59bfb2e..c6c3ee6 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -58,7 +58,6 @@
     <uses-permission android:name="android.permission.ACCEPT_HANDOVER" />
     <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
     <uses-permission android:name="android.permission.BODY_SENSORS" />
-    <uses-permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE" />
     <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
     <uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
     <uses-permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 3007d4a..7a1d9a3 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -156,9 +156,10 @@
         "WifiTrackerLib",
         "WindowManager-Shell",
         "SystemUIAnimationLib",
+        "SystemUICommon",
+        "SystemUICustomizationLib",
         "SystemUIPluginLib",
         "SystemUISharedLib",
-        "SystemUICustomizationLib",
         "SystemUI-statsd",
         "SettingsLib",
         "androidx.core_core-ktx",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 36a0b5d..dabb578 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -347,15 +347,6 @@
 
     <uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" />
 
-    <!-- Intent Chooser -->
-    <permission
-        android:name="android.permission.ADD_CHOOSER_PINS"
-        android:protectionLevel="signature" />
-    <uses-permission android:name="android.permission.ADD_CHOOSER_PINS" />
-    <permission
-        android:name="android.permission.RECEIVE_CHOOSER_PIN_MIGRATION"
-        android:protectionLevel="signature" />
-
     <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/preferences_action_bar.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/preferences_action_bar.xml
new file mode 100644
index 0000000..1d670660
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/preferences_action_bar.xml
@@ -0,0 +1,29 @@
+<?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
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/action_bar_title"
+        style="@style/TextAppearance.AppCompat.Title"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="center_vertical"
+        android:maxLines="5"/>
+</LinearLayout>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index 02d279f..5ed450a 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.accessibility.accessibilitymenu.activity;
 
+import android.app.ActionBar;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
@@ -24,6 +25,7 @@
 import android.os.Bundle;
 import android.provider.Browser;
 import android.provider.Settings;
+import android.widget.TextView;
 import android.view.View;
 
 import androidx.annotation.Nullable;
@@ -46,6 +48,13 @@
                 .beginTransaction()
                 .replace(android.R.id.content, new A11yMenuPreferenceFragment())
                 .commit();
+
+        ActionBar actionBar = getActionBar();
+        actionBar.setDisplayShowCustomEnabled(true);
+        actionBar.setCustomView(R.layout.preferences_action_bar);
+        ((TextView) findViewById(R.id.action_bar_title)).setText(
+                getResources().getString(R.string.accessibility_menu_settings_name)
+        );
     }
 
     /**
diff --git a/packages/SystemUI/common/.gitignore b/packages/SystemUI/common/.gitignore
new file mode 100644
index 0000000..f9a33db
--- /dev/null
+++ b/packages/SystemUI/common/.gitignore
@@ -0,0 +1,9 @@
+.idea/
+.gradle/
+gradle/
+build/
+gradlew*
+local.properties
+*.iml
+android.properties
+buildSrc
\ No newline at end of file
diff --git a/packages/SystemUI/common/Android.bp b/packages/SystemUI/common/Android.bp
new file mode 100644
index 0000000..e36ada8
--- /dev/null
+++ b/packages/SystemUI/common/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+
+    name: "SystemUICommon",
+
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "androidx.core_core-ktx",
+    ],
+
+    manifest: "AndroidManifest.xml",
+    kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SystemUI/common/AndroidManifest.xml b/packages/SystemUI/common/AndroidManifest.xml
new file mode 100644
index 0000000..6f757eb
--- /dev/null
+++ b/packages/SystemUI/common/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.systemui.common">
+
+</manifest>
diff --git a/packages/SystemUI/common/OWNERS b/packages/SystemUI/common/OWNERS
new file mode 100644
index 0000000..9b8a79e
--- /dev/null
+++ b/packages/SystemUI/common/OWNERS
@@ -0,0 +1,2 @@
+darrellshi@google.com
+evanlaird@google.com
diff --git a/packages/SystemUI/common/README.md b/packages/SystemUI/common/README.md
new file mode 100644
index 0000000..1cc5277
--- /dev/null
+++ b/packages/SystemUI/common/README.md
@@ -0,0 +1,5 @@
+# SystemUICommon
+
+`SystemUICommon` is a module within SystemUI that hosts standalone helper libraries. It is intended to be used by other modules, and therefore should not have other SystemUI dependencies to avoid circular dependencies.
+
+To maintain the structure of this module, please refrain from adding components at the top level. Instead, add them to specific sub-packages, such as `systemui/common/buffer/`. This will help to keep the module organized and easy to navigate.
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt b/packages/SystemUI/common/src/com/android/systemui/common/buffer/RingBuffer.kt
similarity index 98%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt
rename to packages/SystemUI/common/src/com/android/systemui/common/buffer/RingBuffer.kt
index 4773f54..de49d1c 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt
+++ b/packages/SystemUI/common/src/com/android/systemui/common/buffer/RingBuffer.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.plugins.util
+package com.android.systemui.common.buffer
 
 import kotlin.math.max
 
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index fb1c454..e306d4a 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -37,6 +37,7 @@
         "error_prone_annotations",
         "PluginCoreLib",
         "SystemUIAnimationLib",
+        "SystemUICommon",
     ],
 
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
index 52dfc55..f71c137 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
@@ -4,7 +4,7 @@
 import androidx.annotation.VisibleForTesting
 
 class WeatherData
-private constructor(
+constructor(
     val description: String,
     val state: WeatherStateIcon,
     val useCelsius: Boolean,
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
index 3e34885..4a6e0b6 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
@@ -18,7 +18,7 @@
 
 import android.os.Trace
 import android.util.Log
-import com.android.systemui.plugins.util.RingBuffer
+import com.android.systemui.common.buffer.RingBuffer
 import com.google.errorprone.annotations.CompileTimeConstant
 import java.io.PrintWriter
 import java.util.concurrent.ArrayBlockingQueue
diff --git a/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml
new file mode 100644
index 0000000..61a1cb5
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml
@@ -0,0 +1,165 @@
+<?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
+  -->
+<animated-vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46">
+            <group android:name="_R_G">
+                <group android:name="_R_G_L_1_G" android:translateX="3.75" android:translateY="8.25">
+                    <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/>
+                    <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/>
+                    <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/>
+                    <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/>
+                </group>
+                <group android:name="_R_G_L_0_G" android:translateX="20.357" android:translateY="35.75" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866">
+                    <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#FF000000" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/>
+                </group>
+            </group>
+            <group android:name="time_group"/>
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " android:valueTo="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData" android:duration="143" android:startOffset="107" android:valueFrom="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.331,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="strokeAlpha" android:duration="140" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="strokeAlpha" android:duration="50" android:startOffset="140" android:valueFrom="1" android:valueTo="0" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " android:valueTo="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="107" android:valueFrom="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_2_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="250" android:startOffset="0" android:valueFrom="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.189,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_3_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="95" android:startOffset="0" android:valueFrom="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " android:valueTo="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData" android:duration="24" android:startOffset="95" android:valueFrom="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueTo="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.833,0.767 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData" android:duration="81" android:startOffset="119" android:valueFrom="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.261,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="fillAlpha" android:duration="120" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="fillAlpha" android:duration="20" android:startOffset="120" android:valueFrom="0" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="scaleX" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleX" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_palette.xml b/packages/SystemUI/res-keyguard/drawable/ic_palette.xml
new file mode 100644
index 0000000..cbea369
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_palette.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M480,880Q398,880 325,848.5Q252,817 197.5,762.5Q143,708 111.5,635Q80,562 80,480Q80,395 112,322Q144,249 199.5,195Q255,141 329.5,110.5Q404,80 489,80Q568,80 639,106.5Q710,133 763.5,180Q817,227 848.5,291.5Q880,356 880,433Q880,541 817,603.5Q754,666 650,666L575,666Q557,666 544,680Q531,694 531,711Q531,738 545.5,757Q560,776 560,801Q560,839 539,859.5Q518,880 480,880ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM247,506Q267,506 282,491Q297,476 297,456Q297,436 282,421Q267,406 247,406Q227,406 212,421Q197,436 197,456Q197,476 212,491Q227,506 247,506ZM373,336Q393,336 408,321Q423,306 423,286Q423,266 408,251Q393,236 373,236Q353,236 338,251Q323,266 323,286Q323,306 338,321Q353,336 373,336ZM587,336Q607,336 622,321Q637,306 637,286Q637,266 622,251Q607,236 587,236Q567,236 552,251Q537,266 537,286Q537,306 552,321Q567,336 587,336ZM718,506Q738,506 753,491Q768,476 768,456Q768,436 753,421Q738,406 718,406Q698,406 683,421Q668,436 668,456Q668,476 683,491Q698,506 718,506ZM480,820Q491,820 495.5,815.5Q500,811 500,801Q500,787 485.5,775Q471,763 471,722Q471,676 501,641Q531,606 577,606L650,606Q726,606 773,561.5Q820,517 820,433Q820,301 720,220.5Q620,140 489,140Q343,140 241.5,238.5Q140,337 140,480Q140,621 239.5,720.5Q339,820 480,820Z"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
index 951d6fe..f3325ec 100644
--- a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
+++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
@@ -104,4 +104,9 @@
         android:fromId="@id/unlocked"
         android:toId="@id/locked"
         android:drawable="@drawable/unlocked_to_locked" />
+
+    <transition
+        android:fromId="@id/locked_fp"
+        android:toId="@id/locked"
+        android:drawable="@drawable/fp_to_locked" />
 </animated-selector>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 2143fc4..edd3047 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -21,19 +21,19 @@
     <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=30] -->
     <string name="keyguard_enter_your_pin">Enter your PIN</string>
 
-    <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=26] -->
+    <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=48] -->
     <string name="keyguard_enter_pin">Enter PIN</string>
 
     <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=30] -->
     <string name="keyguard_enter_your_pattern">Enter your pattern</string>
 
-    <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=26] -->
+    <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=48] -->
     <string name="keyguard_enter_pattern">Draw pattern</string>
 
     <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=30] -->
     <string name="keyguard_enter_your_password">Enter your password</string>
 
-    <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=26] -->
+    <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=48] -->
     <string name="keyguard_enter_password">Enter password</string>
 
     <!-- Shown in the lock screen when there is SIM card IO error. -->
@@ -128,103 +128,103 @@
     <!-- Message shown when user enters wrong pattern -->
     <string name="kg_wrong_pattern">Wrong pattern</string>
 
-    <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] -->
+    <!-- Message shown when user enters wrong pattern [CHAR LIMIT=48] -->
     <string name="kg_wrong_pattern_try_again">Wrong pattern. Try again.</string>
 
     <!-- Message shown when user enters wrong password -->
     <string name="kg_wrong_password">Wrong password</string>
 
-    <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] -->
+    <!-- Message shown when user enters wrong pattern [CHAR LIMIT=48] -->
     <string name="kg_wrong_password_try_again">Wrong password. Try again.</string>
 
     <!-- Message shown when user enters wrong PIN -->
     <string name="kg_wrong_pin">Wrong PIN</string>
 
-    <!-- Message shown when user enters wrong PIN  [CHAR LIMIT=26] -->
+    <!-- Message shown when user enters wrong PIN  [CHAR LIMIT=48] -->
     <string name="kg_wrong_pin_try_again">Wrong PIN. Try again.</string>
 
-    <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2.  [CHAR LIMIT=52] -->
+    <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2.  [CHAR LIMIT=70] -->
     <string name="kg_wrong_input_try_fp_suggestion">Or unlock with fingerprint</string>
 
-    <!-- Message shown when user fingerprint is not recognized  [CHAR LIMIT=26] -->
+    <!-- Message shown when user fingerprint is not recognized  [CHAR LIMIT=48] -->
     <string name="kg_fp_not_recognized">Fingerprint not recognized</string>
 
-    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=26] -->
+    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=48] -->
     <string name="bouncer_face_not_recognized">Face not recognized</string>
 
-    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=52] -->
+    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=70] -->
     <string name="kg_bio_try_again_or_pin">Try again or enter PIN</string>
 
-    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=52] -->
+    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=70] -->
     <string name="kg_bio_try_again_or_password">Try again or enter password</string>
 
-    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=52] -->
+    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=70] -->
     <string name="kg_bio_try_again_or_pattern">Try again or draw pattern</string>
 
-    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=52] -->
+    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=70] -->
     <string name="kg_bio_too_many_attempts_pin">PIN is required after too many attempts</string>
 
-    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=52] -->
+    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=70] -->
     <string name="kg_bio_too_many_attempts_password">Password is required after too many attempts</string>
 
-    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=52] -->
+    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=70] -->
     <string name="kg_bio_too_many_attempts_pattern">Pattern is required after too many attempts</string>
 
-    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26]  -->
+    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48]  -->
     <string name="kg_unlock_with_pin_or_fp">Unlock with PIN or fingerprint</string>
 
-    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26]  -->
+    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48]  -->
     <string name="kg_unlock_with_password_or_fp">Unlock with password or fingerprint</string>
 
-    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26]  -->
+    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48]  -->
     <string name="kg_unlock_with_pattern_or_fp">Unlock with pattern or fingerprint</string>
 
-    <!-- Message shown when we are on bouncer after Device admin requested lockdown.  [CHAR LIMIT=52] -->
+    <!-- Message shown when we are on bouncer after Device admin requested lockdown.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_after_dpm_lock">For added security, device was locked by work policy</string>
 
-    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=52] -->
+    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_after_user_lockdown_pin">PIN is required after lockdown</string>
 
-    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=52] -->
+    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_after_user_lockdown_password">Password is required after lockdown</string>
 
-    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=52] -->
+    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_after_user_lockdown_pattern">Pattern is required after lockdown</string>
 
-    <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update.  [CHAR LIMIT=52] -->
+    <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_unattended_update">Update will install during inactive hours</string>
 
-    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=52] -->
+    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_pin_auth_timeout">Added security required. PIN not used for a while.</string>
 
-    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=52] -->
+    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_password_auth_timeout">Added security required. Password not used for a while.</string>
 
-    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=52] -->
+    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=76] -->
     <string name="kg_prompt_pattern_auth_timeout">Added security required. Pattern not used for a while.</string>
 
-    <!-- Message shown when device hasn't been unlocked for a while.  [CHAR LIMIT=52] -->
+    <!-- Message shown when device hasn't been unlocked for a while.  [CHAR LIMIT=82] -->
     <string name="kg_prompt_auth_timeout">Added security required. Device wasn\u2019t unlocked for a while.</string>
 
-    <!-- Message shown when face unlock is not available after too many failed face authentication attempts.  [CHAR LIMIT=52] -->
+    <!-- Message shown when face unlock is not available after too many failed face authentication attempts.  [CHAR LIMIT=70] -->
     <string name="kg_face_locked_out">Can\u2019t unlock with face. Too many attempts.</string>
 
-    <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts.  [CHAR LIMIT=52] -->
+    <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts.  [CHAR LIMIT=75] -->
     <string name="kg_fp_locked_out">Can\u2019t unlock with fingerprint. Too many attempts.</string>
 
-    <!-- Message shown when Trust Agent is disabled.  [CHAR LIMIT=52] -->
+    <!-- Message shown when Trust Agent is disabled.  [CHAR LIMIT=70] -->
     <string name="kg_trust_agent_disabled">Trust agent is unavailable</string>
 
-    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52]  -->
+    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70]  -->
     <string name="kg_primary_auth_locked_out_pin">Too many attempts with incorrect PIN</string>
 
-    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52]  -->
+    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70]  -->
     <string name="kg_primary_auth_locked_out_pattern">Too many attempts with incorrect pattern</string>
 
-    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52]  -->
+    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70]  -->
     <string name="kg_primary_auth_locked_out_password">Too many attempts with incorrect password</string>
 
-    <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=26]-->
+    <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=48]-->
     <string name="kg_too_many_failed_attempts_countdown">{count, plural,
         =1 {Try again in # second.}
         other {Try again in # seconds.}
@@ -296,13 +296,13 @@
     <!-- Description of airplane mode -->
     <string name="airplane_mode">Airplane mode</string>
 
-    <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=52] -->
+    <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=70] -->
     <string name="kg_prompt_reason_restart_pattern">Pattern is required after device restarts</string>
 
-    <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=52] -->
+    <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=70] -->
     <string name="kg_prompt_reason_restart_pin">PIN is required after device restarts</string>
 
-    <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=52] -->
+    <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=70] -->
     <string name="kg_prompt_reason_restart_password">Password is required after device restarts</string>
 
     <!-- An explanation text that the pattern needs to be solved since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
diff --git a/packages/SystemUI/res/anim/keyguard_settings_popup_ease_out_interpolator.xml b/packages/SystemUI/res/anim/keyguard_settings_popup_ease_out_interpolator.xml
new file mode 100644
index 0000000..8c2937c
--- /dev/null
+++ b/packages/SystemUI/res/anim/keyguard_settings_popup_ease_out_interpolator.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<pathInterpolator
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:controlX1="0.30"
+    android:controlY1="0.00"
+    android:controlX2="0.33"
+    android:controlY2="1.00" />
diff --git a/packages/SystemUI/res/anim/long_press_lock_screen_popup_enter.xml b/packages/SystemUI/res/anim/long_press_lock_screen_popup_enter.xml
new file mode 100644
index 0000000..5fa8822
--- /dev/null
+++ b/packages/SystemUI/res/anim/long_press_lock_screen_popup_enter.xml
@@ -0,0 +1,49 @@
+<?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.
+  -->
+
+<set
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false">
+
+    <scale
+        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
+        android:duration="200"
+        android:fromXScale="0.5"
+        android:fromYScale="0.5"
+        android:toXScale="1.02"
+        android:toYScale="1.02"
+        android:pivotX="50%"
+        android:pivotY="50%" />
+
+    <scale
+        android:interpolator="@anim/keyguard_settings_popup_ease_out_interpolator"
+        android:startOffset="200"
+        android:duration="200"
+        android:fromXScale="1"
+        android:fromYScale="1"
+        android:toXScale="0.98"
+        android:toYScale="0.98"
+        android:pivotX="50%"
+        android:pivotY="50%" />
+
+    <alpha
+        android:interpolator="@android:anim/linear_interpolator"
+        android:duration="83"
+        android:fromAlpha="0"
+        android:toAlpha="1" />
+
+</set>
diff --git a/packages/SystemUI/res/anim/long_press_lock_screen_popup_exit.xml b/packages/SystemUI/res/anim/long_press_lock_screen_popup_exit.xml
new file mode 100644
index 0000000..a6938de
--- /dev/null
+++ b/packages/SystemUI/res/anim/long_press_lock_screen_popup_exit.xml
@@ -0,0 +1,39 @@
+<?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.
+  -->
+
+<set
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false">
+
+    <scale
+        android:interpolator="@android:anim/accelerate_interpolator"
+        android:duration="233"
+        android:fromXScale="1"
+        android:fromYScale="1"
+        android:toXScale="0.5"
+        android:toYScale="0.5"
+        android:pivotX="50%"
+        android:pivotY="50%" />
+
+    <alpha
+        android:interpolator="@android:anim/linear_interpolator"
+        android:delay="150"
+        android:duration="83"
+        android:fromAlpha="1"
+        android:toAlpha="0" />
+
+</set>
diff --git a/packages/SystemUI/res/drawable/ic_qs_no_calling_sms.xml b/packages/SystemUI/res/drawable/ic_shade_no_calling_sms.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/ic_qs_no_calling_sms.xml
rename to packages/SystemUI/res/drawable/ic_shade_no_calling_sms.xml
diff --git a/packages/SystemUI/res/drawable/ic_qs_sim_card.xml b/packages/SystemUI/res/drawable/ic_shade_sim_card.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/ic_qs_sim_card.xml
rename to packages/SystemUI/res/drawable/ic_shade_sim_card.xml
diff --git a/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml b/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml
index 3807b92..a0ceb81 100644
--- a/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml
+++ b/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml
@@ -17,17 +17,17 @@
 <ripple
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:color="?android:attr/colorControlHighlight">
+    android:color="#4d000000">
     <item android:id="@android:id/mask">
         <shape android:shape="rectangle">
             <solid android:color="@android:color/white"/>
-            <corners android:radius="28dp" />
+            <corners android:radius="@dimen/keyguard_affordance_fixed_radius" />
         </shape>
     </item>
     <item>
         <shape android:shape="rectangle">
-            <solid android:color="?androidprv:attr/colorSurface" />
-            <corners android:radius="28dp" />
+            <solid android:color="?androidprv:attr/materialColorOnBackground" />
+            <corners android:radius="@dimen/keyguard_affordance_fixed_radius" />
         </shape>
     </item>
 </ripple>
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index dffe40b..441f963 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -94,7 +94,7 @@
 
     <include
         android:id="@+id/carrier_group"
-        layout="@layout/qs_carrier_group"
+        layout="@layout/shade_carrier_group"
         app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
         android:minHeight="@dimen/large_screen_shade_header_min_height"
         app:layout_constraintWidth_min="48dp"
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 4048a39..e9acf3f 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -20,7 +20,7 @@
     android:id="@+id/keyguard_bottom_area"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
-    android:outlineProvider="none" > <!-- Put it above the status bar header -->
+    android:outlineProvider="none" >
 
     <LinearLayout
         android:id="@+id/keyguard_indication_area"
@@ -59,33 +59,55 @@
 
     </LinearLayout>
 
-    <com.android.systemui.animation.view.LaunchableImageView
-        android:id="@+id/start_button"
-        android:layout_height="@dimen/keyguard_affordance_fixed_height"
-        android:layout_width="@dimen/keyguard_affordance_fixed_width"
-        android:layout_gravity="bottom|start"
-        android:scaleType="fitCenter"
-        android:padding="@dimen/keyguard_affordance_fixed_padding"
-        android:tint="?android:attr/textColorPrimary"
-        android:background="@drawable/keyguard_bottom_affordance_bg"
-        android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
-        android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:layout_gravity="bottom"
+        android:layout_marginHorizontal="@dimen/keyguard_affordance_horizontal_offset"
         android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
-        android:visibility="gone" />
+        android:gravity="bottom"
+        >
 
-    <com.android.systemui.animation.view.LaunchableImageView
-        android:id="@+id/end_button"
-        android:layout_height="@dimen/keyguard_affordance_fixed_height"
-        android:layout_width="@dimen/keyguard_affordance_fixed_width"
-        android:layout_gravity="bottom|end"
-        android:scaleType="fitCenter"
-        android:padding="@dimen/keyguard_affordance_fixed_padding"
-        android:tint="?android:attr/textColorPrimary"
-        android:background="@drawable/keyguard_bottom_affordance_bg"
-        android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
-        android:layout_marginEnd="@dimen/keyguard_affordance_horizontal_offset"
-        android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
-        android:visibility="gone" />
+        <com.android.systemui.animation.view.LaunchableImageView
+            android:id="@+id/start_button"
+            android:layout_height="@dimen/keyguard_affordance_fixed_height"
+            android:layout_width="@dimen/keyguard_affordance_fixed_width"
+            android:scaleType="fitCenter"
+            android:padding="@dimen/keyguard_affordance_fixed_padding"
+            android:tint="?android:attr/textColorPrimary"
+            android:background="@drawable/keyguard_bottom_affordance_bg"
+            android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
+            android:visibility="invisible" />
+
+        <FrameLayout
+            android:layout_width="0dp"
+            android:layout_weight="1"
+            android:layout_height="wrap_content"
+            android:paddingHorizontal="24dp"
+            >
+            <include
+                android:id="@+id/keyguard_settings_button"
+                layout="@layout/keyguard_settings_popup_menu"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:visibility="gone"
+                />
+        </FrameLayout>
+
+        <com.android.systemui.animation.view.LaunchableImageView
+            android:id="@+id/end_button"
+            android:layout_height="@dimen/keyguard_affordance_fixed_height"
+            android:layout_width="@dimen/keyguard_affordance_fixed_width"
+            android:scaleType="fitCenter"
+            android:padding="@dimen/keyguard_affordance_fixed_padding"
+            android:tint="?android:attr/textColorPrimary"
+            android:background="@drawable/keyguard_bottom_affordance_bg"
+            android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
+            android:visibility="invisible" />
+
+    </LinearLayout>
 
     <FrameLayout
         android:id="@+id/overlay_container"
diff --git a/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
index 89d88fe..65ee8b3 100644
--- a/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
+++ b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
@@ -15,25 +15,24 @@
   ~
   -->
 
-<LinearLayout
+<com.android.systemui.animation.view.LaunchableLinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:minHeight="52dp"
+    android:minHeight="@dimen/keyguard_affordance_fixed_height"
     android:orientation="horizontal"
     android:gravity="center_vertical"
     android:background="@drawable/keyguard_settings_popup_menu_background"
-    android:paddingStart="16dp"
-    android:paddingEnd="24dp"
-    android:paddingVertical="16dp">
+    android:padding="12dp">
 
     <ImageView
         android:id="@+id/icon"
-        android:layout_width="20dp"
-        android:layout_height="20dp"
-        android:layout_marginEnd="16dp"
-        android:tint="?android:attr/textColorPrimary"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_marginEnd="8dp"
+        android:tint="?androidprv:attr/materialColorOnSecondaryFixed"
         android:importantForAccessibility="no"
         tools:ignore="UseAppTint" />
 
@@ -42,9 +41,9 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:textAppearance="?android:attr/textAppearanceMedium"
-        android:textColor="?android:attr/textColorPrimary"
+        android:textColor="?androidprv:attr/materialColorOnSecondaryFixed"
         android:textSize="14sp"
         android:maxLines="1"
         android:ellipsize="end" />
 
-</LinearLayout>
\ No newline at end of file
+</com.android.systemui.animation.view.LaunchableLinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_carrier.xml b/packages/SystemUI/res/layout/shade_carrier.xml
similarity index 93%
rename from packages/SystemUI/res/layout/qs_carrier.xml
rename to packages/SystemUI/res/layout/shade_carrier.xml
index a854660..0fed393 100644
--- a/packages/SystemUI/res/layout/qs_carrier.xml
+++ b/packages/SystemUI/res/layout/shade_carrier.xml
@@ -14,7 +14,7 @@
   ~ limitations under the License
   -->
 
-<com.android.systemui.qs.carrier.QSCarrier
+<com.android.systemui.shade.carrier.ShadeCarrier
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/linear_carrier"
     android:layout_width="wrap_content"
@@ -29,7 +29,7 @@
     android:focusable="true" >
 
     <com.android.systemui.util.AutoMarqueeTextView
-        android:id="@+id/qs_carrier_text"
+        android:id="@+id/shade_carrier_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_weight="1"
@@ -53,4 +53,4 @@
         android:layout_marginStart="@dimen/qs_carrier_margin_width"
         android:visibility="gone" />
 
-</com.android.systemui.qs.carrier.QSCarrier>
\ No newline at end of file
+</com.android.systemui.shade.carrier.ShadeCarrier>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_carrier_group.xml b/packages/SystemUI/res/layout/shade_carrier_group.xml
similarity index 87%
rename from packages/SystemUI/res/layout/qs_carrier_group.xml
rename to packages/SystemUI/res/layout/shade_carrier_group.xml
index 6e13ab9..2e8f98c 100644
--- a/packages/SystemUI/res/layout/qs_carrier_group.xml
+++ b/packages/SystemUI/res/layout/shade_carrier_group.xml
@@ -15,7 +15,7 @@
   -->
 
 <!-- Extends LinearLayout -->
-<com.android.systemui.qs.carrier.QSCarrierGroup
+<com.android.systemui.shade.carrier.ShadeCarrierGroup
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/qs_mobile"
     android:layout_width="0dp"
@@ -39,25 +39,25 @@
         android:visibility="gone"/>
 
     <include
-        layout="@layout/qs_carrier"
+        layout="@layout/shade_carrier"
         android:id="@+id/carrier1"
         android:layout_weight="1"/>
 
     <View
-        android:id="@+id/qs_carrier_divider1"
+        android:id="@+id/shade_carrier_divider1"
         android:layout_width="@dimen/qs_header_carrier_separator_width"
         android:layout_height="match_parent"
         android:visibility="gone"
         android:importantForAccessibility="no"/>
 
     <include
-        layout="@layout/qs_carrier"
+        layout="@layout/shade_carrier"
         android:id="@+id/carrier2"
         android:layout_weight="1"
         android:visibility="gone"/>
 
     <View
-        android:id="@+id/qs_carrier_divider2"
+        android:id="@+id/shade_carrier_divider2"
         android:layout_width="@dimen/qs_header_carrier_separator_width"
         android:layout_height="match_parent"
         android:layout_weight="1"
@@ -65,9 +65,9 @@
         android:importantForAccessibility="no"/>
 
     <include
-        layout="@layout/qs_carrier"
+        layout="@layout/shade_carrier"
         android:id="@+id/carrier3"
         android:layout_weight="1"
         android:visibility="gone"/>
 
-</com.android.systemui.qs.carrier.QSCarrierGroup>
\ No newline at end of file
+</com.android.systemui.shade.carrier.ShadeCarrierGroup>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index a11ffcd..d710676 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -120,10 +120,6 @@
         />
     </com.android.systemui.shade.NotificationsQuickSettingsContainer>
 
-    <include
-        layout="@layout/keyguard_bottom_area"
-        android:visibility="gone" />
-
     <ViewStub
         android:id="@+id/keyguard_user_switcher_stub"
         android:layout="@layout/keyguard_user_switcher"
@@ -153,9 +149,7 @@
 
     </com.android.keyguard.LockIconView>
 
-    <FrameLayout
-        android:id="@+id/preview_container"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-    </FrameLayout>
+    <include
+        layout="@layout/keyguard_bottom_area"
+        android:visibility="gone" />
 </com.android.systemui.shade.NotificationPanelView>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 714d495..4db42aa 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1774,13 +1774,6 @@
     <dimen name="rear_display_title_top_padding">24dp</dimen>
     <dimen name="rear_display_title_bottom_padding">16dp</dimen>
 
-    <!--
-    Vertical distance between the pointer and the popup menu that shows up on the lock screen when
-    it is long-pressed.
-    -->
-    <dimen name="keyguard_long_press_settings_popup_vertical_offset">96dp</dimen>
-
-
     <!-- Bouncer user switcher margins -->
     <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
     <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index befbfab..675ae32 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -36,7 +36,7 @@
          fade_out_complete_frame -->
     <dimen name="percent_displacement_at_fade_out" format="float">0.1066</dimen>
 
-    <integer name="qs_carrier_max_em">7</integer>
+    <integer name="shade_carrier_max_em">7</integer>
 
     <!-- Maximum number of notification icons shown on the Always on Display
         (excluding overflow dot) -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f1777f8..74ae954 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3057,13 +3057,17 @@
     <string name="call_from_work_profile_close">Close</string>
 
     <!--
-    Label for a menu item in a menu that is shown when the user wishes to configure the lock screen.
+    Label for a menu item in a menu that is shown when the user wishes to customize the lock screen.
     Clicking on this menu item takes the user to a screen where they can modify the settings of the
     lock screen.
 
+    It is critical that this text is as short as possible. If needed, translators should feel free
+    to drop "lock screen" from their translation and keep just "Customize" or "Customization", in
+    cases when that verb is not available in their target language.
+
     [CHAR LIMIT=32]
     -->
-    <string name="lock_screen_settings">Lock screen settings</string>
+    <string name="lock_screen_settings">Customize lock screen</string>
 
     <!-- Content description for Wi-Fi not available icon on dream [CHAR LIMIT=NONE]-->
     <string name="wifi_unavailable_dream_overlay_content_description">Wi-Fi not available</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 064cea1..2098aea 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1399,4 +1399,9 @@
     <style name="ShortcutItemBackground">
         <item name="android:background">@color/ksh_key_item_new_background</item>
     </style>
+
+    <style name="LongPressLockScreenAnimation">
+        <item name="android:windowEnterAnimation">@anim/long_press_lock_screen_popup_enter</item>
+        <item name="android:windowExitAnimation">@anim/long_press_lock_screen_popup_exit</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 4d7d0ea..3c447a8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -124,6 +124,8 @@
     public static final int SYSUI_STATE_WAKEFULNESS_TRANSITION = 1 << 29;
     // The notification panel expansion fraction is > 0
     public static final int SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE = 1 << 30;
+    // When keyguard will be dismissed but didn't start animation yet
+    public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY = 1 << 31;
 
     // Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and
     // SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants
@@ -172,6 +174,7 @@
             SYSUI_STATE_AWAKE,
             SYSUI_STATE_WAKEFULNESS_TRANSITION,
             SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
+            SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY,
     })
     public @interface SystemUiStateFlags {}
 
@@ -270,6 +273,9 @@
         if ((flags & SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE) != 0) {
             str.add("notif_visible");
         }
+        if ((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY) != 0) {
+            str.add("keygrd_going_away");
+        }
 
         return str.toString();
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index 7971e84..b153785 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -21,7 +21,7 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
-import android.net.wifi.WifiManager;
+import android.os.Trace;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
@@ -37,6 +37,7 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository;
 import com.android.systemui.telephony.TelephonyListenerManager;
 
 import java.util.List;
@@ -50,7 +51,10 @@
  * Controller that generates text including the carrier names and/or the status of all the SIM
  * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
  * separated by a given separator {@link CharSequence}.
+ *
+ * @deprecated use {@link com.android.systemui.statusbar.pipeline.wifi} instead
  */
+@Deprecated
 public class CarrierTextManager {
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
     private static final String TAG = "CarrierTextController";
@@ -64,7 +68,7 @@
     private final AtomicBoolean mNetworkSupported = new AtomicBoolean();
     @VisibleForTesting
     protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private final WifiManager mWifiManager;
+    private final WifiRepository mWifiRepository;
     private final boolean[] mSimErrorState;
     private final int mSimSlotsNumber;
     @Nullable // Check for nullability before dispatching
@@ -165,7 +169,7 @@
             CharSequence separator,
             boolean showAirplaneMode,
             boolean showMissingSim,
-            @Nullable WifiManager wifiManager,
+            WifiRepository wifiRepository,
             TelephonyManager telephonyManager,
             TelephonyListenerManager telephonyListenerManager,
             WakefulnessLifecycle wakefulnessLifecycle,
@@ -177,8 +181,7 @@
 
         mShowAirplaneMode = showAirplaneMode;
         mShowMissingSim = showMissingSim;
-
-        mWifiManager = wifiManager;
+        mWifiRepository = wifiRepository;
         mTelephonyManager = telephonyManager;
         mSeparator = separator;
         mTelephonyListenerManager = telephonyListenerManager;
@@ -297,6 +300,7 @@
     }
 
     protected void updateCarrierText() {
+        Trace.beginSection("CarrierTextManager#updateCarrierText");
         boolean allSimsMissing = true;
         boolean anySimReadyAndInService = false;
         CharSequence displayText = null;
@@ -329,20 +333,20 @@
                 carrierNames[i] = carrierTextForSimState;
             }
             if (simState == TelephonyManager.SIM_STATE_READY) {
+                Trace.beginSection("WFC check");
                 ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
                 if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
                     // hack for WFC (IWLAN) not turning off immediately once
                     // Wi-Fi is disassociated or disabled
                     if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
-                            || (mWifiManager != null && mWifiManager.isWifiEnabled()
-                            && mWifiManager.getConnectionInfo() != null
-                            && mWifiManager.getConnectionInfo().getBSSID() != null)) {
+                            || mWifiRepository.isWifiConnectedWithValidSsid()) {
                         if (DEBUG) {
                             Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
                         }
                         anySimReadyAndInService = true;
                     }
                 }
+                Trace.endSection();
             }
         }
         // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY
@@ -406,6 +410,7 @@
                 subsIds,
                 airplaneMode);
         postToCallback(info);
+        Trace.endSection();
     }
 
     @VisibleForTesting
@@ -633,7 +638,7 @@
     public static class Builder {
         private final Context mContext;
         private final String mSeparator;
-        private final WifiManager mWifiManager;
+        private final WifiRepository mWifiRepository;
         private final TelephonyManager mTelephonyManager;
         private final TelephonyListenerManager mTelephonyListenerManager;
         private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -647,7 +652,7 @@
         public Builder(
                 Context context,
                 @Main Resources resources,
-                @Nullable WifiManager wifiManager,
+                @Nullable WifiRepository wifiRepository,
                 TelephonyManager telephonyManager,
                 TelephonyListenerManager telephonyListenerManager,
                 WakefulnessLifecycle wakefulnessLifecycle,
@@ -657,7 +662,7 @@
             mContext = context;
             mSeparator = resources.getString(
                     com.android.internal.R.string.kg_text_message_separator);
-            mWifiManager = wifiManager;
+            mWifiRepository = wifiRepository;
             mTelephonyManager = telephonyManager;
             mTelephonyListenerManager = telephonyListenerManager;
             mWakefulnessLifecycle = wakefulnessLifecycle;
@@ -681,7 +686,7 @@
         /** Create a CarrierTextManager. */
         public CarrierTextManager build() {
             return new CarrierTextManager(
-                    mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiManager,
+                    mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiRepository,
                     mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle,
                     mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor);
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt
index 3a89c13..40f6f48 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt
@@ -17,9 +17,9 @@
 package com.android.keyguard
 
 import android.annotation.CurrentTimeMillisLong
+import com.android.systemui.common.buffer.RingBuffer
 import com.android.systemui.dump.DumpsysTableLogger
 import com.android.systemui.dump.Row
-import com.android.systemui.plugins.util.RingBuffer
 
 /** Verbose debug information. */
 data class KeyguardActiveUnlockModel(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 9290220..25d1792 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -109,7 +109,7 @@
     private final ContentObserver mShowWeatherObserver = new ContentObserver(null) {
         @Override
         public void onChange(boolean change) {
-            setDateWeatherVisibility();
+            setWeatherVisibility();
         }
     };
 
@@ -236,6 +236,7 @@
 
         updateDoubleLineClock();
         setDateWeatherVisibility();
+        setWeatherVisibility();
 
         mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
                 mKeyguardUnlockAnimationListener);
@@ -266,6 +267,8 @@
                     mStatusArea.removeView(mDateWeatherView);
                     addDateWeatherView(index);
                 }
+                setDateWeatherVisibility();
+                setWeatherVisibility();
             }
             int index = mStatusArea.indexOfChild(mSmartspaceView);
             if (index >= 0) {
@@ -487,16 +490,19 @@
     }
 
     private void setDateWeatherVisibility() {
-        if (mDateWeatherView != null || mWeatherView != null) {
+        if (mDateWeatherView != null) {
             mUiExecutor.execute(() -> {
-                if (mDateWeatherView != null) {
-                    mDateWeatherView.setVisibility(
-                            clockHasCustomWeatherDataDisplay() ? View.GONE : View.VISIBLE);
-                }
-                if (mWeatherView != null) {
-                    mWeatherView.setVisibility(
-                            mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE);
-                }
+                mDateWeatherView.setVisibility(
+                        clockHasCustomWeatherDataDisplay() ? View.GONE : View.VISIBLE);
+            });
+        }
+    }
+
+    private void setWeatherVisibility() {
+        if (mWeatherView != null) {
+            mUiExecutor.execute(() -> {
+                mWeatherView.setVisibility(
+                        mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE);
             });
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
index c98e9b4..5b0e290 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
@@ -17,9 +17,9 @@
 package com.android.keyguard
 
 import android.annotation.CurrentTimeMillisLong
+import com.android.systemui.common.buffer.RingBuffer
 import com.android.systemui.dump.DumpsysTableLogger
 import com.android.systemui.dump.Row
-import com.android.systemui.plugins.util.RingBuffer
 
 /** Verbose debug information associated. */
 data class KeyguardFaceListenModel(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt
index 57130ed..b8c0ccb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt
@@ -17,9 +17,9 @@
 package com.android.keyguard
 
 import android.annotation.CurrentTimeMillisLong
+import com.android.systemui.common.buffer.RingBuffer
 import com.android.systemui.dump.DumpsysTableLogger
 import com.android.systemui.dump.Row
-import com.android.systemui.plugins.util.RingBuffer
 
 /** Verbose debug information. */
 data class KeyguardFingerprintListenModel(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index c4df836..6f54988 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -16,12 +16,37 @@
 
 package com.android.keyguard;
 
+import static androidx.constraintlayout.widget.ConstraintSet.END;
+import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
+
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
 import android.annotation.Nullable;
 import android.graphics.Rect;
+import android.transition.ChangeBounds;
+import android.transition.Transition;
+import android.transition.TransitionListenerAdapter;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.transition.TransitionValues;
 import android.util.Slog;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
 
+import androidx.annotation.VisibleForTesting;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
+
+import com.android.internal.jank.InteractionJankMonitor;
 import com.android.keyguard.KeyguardClockSwitch.ClockSize;
 import com.android.keyguard.logging.KeyguardLogger;
+import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -42,6 +67,12 @@
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
     private static final String TAG = "KeyguardStatusViewController";
 
+    /**
+     * Duration to use for the animator when the keyguard status view alignment changes, and a
+     * custom clock animation is in use.
+     */
+    private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;
+
     private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES =
             new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
 
@@ -50,8 +81,25 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final ConfigurationController mConfigurationController;
     private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
+    private final FeatureFlags mFeatureFlags;
+    private final InteractionJankMonitor mInteractionJankMonitor;
     private final Rect mClipBounds = new Rect();
 
+    private Boolean mStatusViewCentered = true;
+
+    private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
+            new TransitionListenerAdapter() {
+                @Override
+                public void onTransitionCancel(Transition transition) {
+                    mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
+                }
+
+                @Override
+                public void onTransitionEnd(Transition transition) {
+                    mInteractionJankMonitor.end(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
+                }
+            };
+
     @Inject
     public KeyguardStatusViewController(
             KeyguardStatusView keyguardStatusView,
@@ -62,7 +110,9 @@
             ConfigurationController configurationController,
             DozeParameters dozeParameters,
             ScreenOffAnimationController screenOffAnimationController,
-            KeyguardLogger logger) {
+            KeyguardLogger logger,
+            FeatureFlags featureFlags,
+            InteractionJankMonitor interactionJankMonitor) {
         super(keyguardStatusView);
         mKeyguardSliceViewController = keyguardSliceViewController;
         mKeyguardClockSwitchController = keyguardClockSwitchController;
@@ -71,6 +121,8 @@
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
                 dozeParameters, screenOffAnimationController, /* animateYPos= */ true,
                 logger.getBuffer());
+        mInteractionJankMonitor = interactionJankMonitor;
+        mFeatureFlags = featureFlags;
     }
 
     @Override
@@ -242,9 +294,141 @@
         }
     }
 
-    /** Gets the current clock controller. */
-    @Nullable
-    public ClockController getClockController() {
-        return mKeyguardClockSwitchController.getClock();
+    /**
+     * Updates the alignment of the KeyguardStatusView and animates the transition if requested.
+     */
+    public void updateAlignment(
+            ConstraintLayout notifContainerParent,
+            boolean splitShadeEnabled,
+            boolean shouldBeCentered,
+            boolean animate) {
+        if (mStatusViewCentered == shouldBeCentered) {
+            return;
+        }
+
+        mStatusViewCentered = shouldBeCentered;
+        if (notifContainerParent == null) {
+            return;
+        }
+
+        ConstraintSet constraintSet = new ConstraintSet();
+        constraintSet.clone(notifContainerParent);
+        int statusConstraint = shouldBeCentered ? PARENT_ID : R.id.qs_edge_guideline;
+        constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END);
+        if (!animate) {
+            constraintSet.applyTo(notifContainerParent);
+            return;
+        }
+
+        mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
+        ChangeBounds transition = new ChangeBounds();
+        if (splitShadeEnabled) {
+            // Excluding media from the transition on split-shade, as it doesn't transition
+            // horizontally properly.
+            transition.excludeTarget(R.id.status_view_media_container, true);
+        }
+
+        transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+
+        ClockController clock = mKeyguardClockSwitchController.getClock();
+        boolean customClockAnimation = clock != null
+                && clock.getConfig().getHasCustomPositionUpdatedAnimation();
+
+        if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
+            // Find the clock, so we can exclude it from this transition.
+            FrameLayout clockContainerView = mView.findViewById(R.id.lockscreen_clock_view_large);
+
+            // The clock container can sometimes be null. If it is, just fall back to the
+            // old animation rather than setting up the custom animations.
+            if (clockContainerView == null || clockContainerView.getChildCount() == 0) {
+                transition.addListener(mKeyguardStatusAlignmentTransitionListener);
+                TransitionManager.beginDelayedTransition(notifContainerParent, transition);
+            } else {
+                View clockView = clockContainerView.getChildAt(0);
+
+                transition.excludeTarget(clockView, /* exclude= */ true);
+
+                TransitionSet set = new TransitionSet();
+                set.addTransition(transition);
+
+                SplitShadeTransitionAdapter adapter =
+                        new SplitShadeTransitionAdapter(mKeyguardClockSwitchController);
+
+                // Use linear here, so the actual clock can pick its own interpolator.
+                adapter.setInterpolator(Interpolators.LINEAR);
+                adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION);
+                adapter.addTarget(clockView);
+                set.addTransition(adapter);
+                set.addListener(mKeyguardStatusAlignmentTransitionListener);
+                TransitionManager.beginDelayedTransition(notifContainerParent, set);
+            }
+        } else {
+            transition.addListener(mKeyguardStatusAlignmentTransitionListener);
+            TransitionManager.beginDelayedTransition(notifContainerParent, transition);
+        }
+
+        constraintSet.applyTo(notifContainerParent);
+    }
+
+    @VisibleForTesting
+    static class SplitShadeTransitionAdapter extends Transition {
+        private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds";
+        private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS };
+
+        private final KeyguardClockSwitchController mController;
+
+        @VisibleForTesting
+        SplitShadeTransitionAdapter(KeyguardClockSwitchController controller) {
+            mController = controller;
+        }
+
+        private void captureValues(TransitionValues transitionValues) {
+            Rect boundsRect = new Rect();
+            boundsRect.left = transitionValues.view.getLeft();
+            boundsRect.top = transitionValues.view.getTop();
+            boundsRect.right = transitionValues.view.getRight();
+            boundsRect.bottom = transitionValues.view.getBottom();
+            transitionValues.values.put(PROP_BOUNDS, boundsRect);
+        }
+
+        @Override
+        public void captureEndValues(TransitionValues transitionValues) {
+            captureValues(transitionValues);
+        }
+
+        @Override
+        public void captureStartValues(TransitionValues transitionValues) {
+            captureValues(transitionValues);
+        }
+
+        @Nullable
+        @Override
+        public Animator createAnimator(ViewGroup sceneRoot, @Nullable TransitionValues startValues,
+                @Nullable TransitionValues endValues) {
+            if (startValues == null || endValues == null) {
+                return null;
+            }
+            ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+
+            Rect from = (Rect) startValues.values.get(PROP_BOUNDS);
+            Rect to = (Rect) endValues.values.get(PROP_BOUNDS);
+
+            anim.addUpdateListener(animation -> {
+                ClockController clock = mController.getClock();
+                if (clock == null) {
+                    return;
+                }
+
+                clock.getAnimations().onPositionUpdated(from, to, animation.getAnimatedFraction());
+            });
+
+            return anim;
+        }
+
+        @Override
+        public String[] getTransitionProperties() {
+            return TRANSITION_PROPERTIES;
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index e1bca89..350c4ed 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -96,6 +96,7 @@
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.SensorProperties;
+import android.hardware.biometrics.SensorPropertiesInternal;
 import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorPropertiesInternal;
@@ -1278,6 +1279,9 @@
         if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) {
             lockedOutStateChanged = !mFaceLockedOutPermanent;
             mFaceLockedOutPermanent = true;
+            if (isFaceClass3()) {
+                updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
+            }
         }
 
         if (isHwUnavailable && cameraPrivacyEnabled) {
@@ -1487,8 +1491,10 @@
         // STRONG_AUTH_REQUIRED_AFTER_LOCKOUT which is the same as mFingerprintLockedOutPermanent;
         // however the strong auth tracker does not include the temporary lockout
         // mFingerprintLockedOut.
+        // Class 3 biometric lockout will lockout ALL biometrics
         return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric)
-                && !mFingerprintLockedOut;
+                && (!isFingerprintClass3() || !isFingerprintLockedOut())
+                && (!isFaceClass3() || !mFaceLockedOutPermanent);
     }
 
     /**
@@ -1506,9 +1512,9 @@
             @NonNull BiometricSourceType biometricSourceType) {
         switch (biometricSourceType) {
             case FINGERPRINT:
-                return isUnlockingWithBiometricAllowed(true);
+                return isUnlockingWithBiometricAllowed(isFingerprintClass3());
             case FACE:
-                return isUnlockingWithBiometricAllowed(false);
+                return isUnlockingWithBiometricAllowed(isFaceClass3());
             default:
                 return false;
         }
@@ -2473,7 +2479,7 @@
     }
 
     private void updateFaceEnrolled(int userId) {
-        Boolean isFaceEnrolled = mFaceManager != null && !mFaceSensorProperties.isEmpty()
+        final Boolean isFaceEnrolled = isFaceSupported()
                 && mBiometricEnabledForUser.get(userId)
                 && mAuthController.isFaceAuthEnrolled(userId);
         if (mIsFaceEnrolled != isFaceEnrolled) {
@@ -2482,10 +2488,14 @@
         mIsFaceEnrolled = isFaceEnrolled;
     }
 
-    public boolean isFaceSupported() {
+    private boolean isFaceSupported() {
         return mFaceManager != null && !mFaceSensorProperties.isEmpty();
     }
 
+    private boolean isFingerprintSupported() {
+        return mFpm != null && !mFingerprintSensorProperties.isEmpty();
+    }
+
     /**
      * @return true if there's at least one udfps enrolled for the current user.
      */
@@ -2792,10 +2802,10 @@
                 || !mLockPatternUtils.isSecure(user);
 
         // Don't trigger active unlock if fp is locked out
-        final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
+        final boolean fpLockedOut = isFingerprintLockedOut();
 
         // Don't trigger active unlock if primary auth is required
-        final boolean primaryAuthRequired = !isUnlockingWithBiometricAllowed(true);
+        final boolean primaryAuthRequired = !isUnlockingWithTrustAgentAllowed();
 
         final boolean shouldTriggerActiveUnlock =
                 (mAuthInterruptActive || triggerActiveUnlockForAssistant || awakeKeyguard)
@@ -2857,7 +2867,7 @@
                         || mGoingToSleep
                         || shouldListenForFingerprintAssistant
                         || (mKeyguardOccluded && mIsDreaming)
-                        || (mKeyguardOccluded && userDoesNotHaveTrust
+                        || (mKeyguardOccluded && userDoesNotHaveTrust && mKeyguardShowing
                             && (mOccludingAppRequestingFp || isUdfps || mAlternateBouncerShowing));
 
         // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
@@ -2949,7 +2959,7 @@
         // allow face detection to happen even if stronger auth is required. When face is detected,
         // we show the bouncer. However, if the user manually locked down the device themselves,
         // never attempt to detect face.
-        final boolean supportsDetect = !mFaceSensorProperties.isEmpty()
+        final boolean supportsDetect = isFaceSupported()
                 && mFaceSensorProperties.get(0).supportsFaceDetection
                 && canBypass && !mPrimaryBouncerIsOrWillBeShowing
                 && !isUserInLockdown(user);
@@ -3104,7 +3114,7 @@
                                     : WAKE_REASON_UNKNOWN
                     ).toFaceAuthenticateOptions();
             // This would need to be updated for multi-sensor devices
-            final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty()
+            final boolean supportsFaceDetection = isFaceSupported()
                     && mFaceSensorProperties.get(0).supportsFaceDetection;
             if (!isUnlockingWithBiometricAllowed(FACE)) {
                 final boolean udfpsFingerprintAuthRunning = isUdfpsSupported()
@@ -3166,21 +3176,15 @@
      * @return {@code true} if possible.
      */
     public boolean isUnlockingWithNonStrongBiometricsPossible(int userId) {
-        // This assumes that there is at most one face and at most one fingerprint sensor
-        return (mFaceManager != null && !mFaceSensorProperties.isEmpty()
-                && (mFaceSensorProperties.get(0).sensorStrength != SensorProperties.STRENGTH_STRONG)
-                && isUnlockWithFacePossible(userId))
-                || (mFpm != null && !mFingerprintSensorProperties.isEmpty()
-                && (mFingerprintSensorProperties.get(0).sensorStrength
-                != SensorProperties.STRENGTH_STRONG) && isUnlockWithFingerprintPossible(userId));
+        return (!isFaceClass3() && isUnlockWithFacePossible(userId))
+                || (isFingerprintClass3() && isUnlockWithFingerprintPossible(userId));
     }
 
     @SuppressLint("MissingPermission")
     @VisibleForTesting
     boolean isUnlockWithFingerprintPossible(int userId) {
         // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
-        boolean newFpEnrolled = mFpm != null
-                && !mFingerprintSensorProperties.isEmpty()
+        boolean newFpEnrolled = isFingerprintSupported()
                 && !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId);
         Boolean oldFpEnrolled = mIsUnlockWithFingerprintPossible.getOrDefault(userId, false);
         if (oldFpEnrolled != newFpEnrolled) {
@@ -3330,12 +3334,12 @@
 
         // Immediately stop previous biometric listening states.
         // Resetting lockout states updates the biometric listening states.
-        if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) {
+        if (isFaceSupported()) {
             stopListeningForFace(FACE_AUTH_UPDATED_USER_SWITCHING);
             handleFaceLockoutReset(mFaceManager.getLockoutModeForUser(
                     mFaceSensorProperties.get(0).sensorId, userId));
         }
-        if (mFpm != null && !mFingerprintSensorProperties.isEmpty()) {
+        if (isFingerprintSupported()) {
             stopListeningForFingerprint();
             handleFingerprintLockoutReset(mFpm.getLockoutModeForUser(
                     mFingerprintSensorProperties.get(0).sensorId, userId));
@@ -4071,6 +4075,22 @@
         return BIOMETRIC_LOCKOUT_RESET_DELAY_MS;
     }
 
+    @VisibleForTesting
+    protected boolean isFingerprintClass3() {
+        // This assumes that there is at most one fingerprint sensor property
+        return isFingerprintSupported() && isClass3Biometric(mFingerprintSensorProperties.get(0));
+    }
+
+    @VisibleForTesting
+    protected boolean isFaceClass3() {
+        // This assumes that there is at most one face sensor property
+        return isFaceSupported() && isClass3Biometric(mFaceSensorProperties.get(0));
+    }
+
+    private boolean isClass3Biometric(SensorPropertiesInternal sensorProperties) {
+        return sensorProperties.sensorStrength == SensorProperties.STRENGTH_STRONG;
+    }
+
     /**
      * Unregister all listeners.
      */
@@ -4122,11 +4142,12 @@
         for (int subId : mServiceStates.keySet()) {
             pw.println("    " + subId + "=" + mServiceStates.get(subId));
         }
-        if (mFpm != null && !mFingerprintSensorProperties.isEmpty()) {
+        if (isFingerprintSupported()) {
             final int userId = mUserTracker.getUserId();
             final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
             BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
             pw.println("  Fingerprint state (user=" + userId + ")");
+            pw.println("    isFingerprintClass3=" + isFingerprintClass3());
             pw.println("    areAllFpAuthenticatorsRegistered="
                     + mAuthController.areAllFingerprintAuthenticatorsRegistered());
             pw.println("    allowed="
@@ -4184,11 +4205,12 @@
                     mFingerprintListenBuffer.toList()
             ).printTableData(pw);
         }
-        if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) {
+        if (isFaceSupported()) {
             final int userId = mUserTracker.getUserId();
             final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
             BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
             pw.println("  Face authentication state (user=" + userId + ")");
+            pw.println("    isFaceClass3=" + isFaceClass3());
             pw.println("    allowed="
                     + (face != null && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric)));
             pw.println("    auth'd="
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index ac0a3fd..a678edc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -28,7 +28,6 @@
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
-import com.android.systemui.statusbar.phone.AnimatorHandle;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -48,7 +47,6 @@
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private boolean mAnimateYPos;
     private boolean mKeyguardViewVisibilityAnimating;
-    private AnimatorHandle mKeyguardAnimatorHandle;
     private boolean mLastOccludedState = false;
     private final AnimationProperties mAnimationProperties = new AnimationProperties();
     private final LogBuffer mLogBuffer;
@@ -85,10 +83,6 @@
             boolean keyguardFadingAway,
             boolean goingToFullShade,
             int oldStatusBarState) {
-        if (mKeyguardAnimatorHandle != null) {
-            mKeyguardAnimatorHandle.cancel();
-            mKeyguardAnimatorHandle = null;
-        }
         mView.animate().cancel();
         boolean isOccluded = mKeyguardStateController.isOccluded();
         mKeyguardViewVisibilityAnimating = false;
@@ -122,7 +116,7 @@
                     .setDuration(320)
                     .setInterpolator(Interpolators.ALPHA_IN)
                     .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
-            log("keyguardFadingAway transition w/ Y Animation");
+            log("keyguardFadingAway transition w/ Y Aniamtion");
         } else if (statusBarState == KEYGUARD) {
             if (keyguardFadingAway) {
                 mKeyguardViewVisibilityAnimating = true;
@@ -154,7 +148,7 @@
 
                 // 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.
-                mKeyguardAnimatorHandle = mScreenOffAnimationController.animateInKeyguard(
+                mScreenOffAnimationController.animateInKeyguard(
                         mView, mAnimateKeyguardStatusViewVisibleEndRunnable);
             } else {
                 log("Direct set Visibility to VISIBLE");
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 235a8bc..5f2afe8 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -294,6 +294,11 @@
 
         final CharSequence prevContentDescription = mView.getContentDescription();
         if (mShowLockIcon) {
+            if (wasShowingFpIcon) {
+                // fp icon was shown by UdfpsView, and now we still want to animate the transition
+                // in this drawable
+                mView.updateIcon(ICON_FINGERPRINT, false);
+            }
             mView.updateIcon(ICON_LOCK, false);
             mView.setContentDescription(mLockedLabel);
             mView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserPinMigration.kt b/packages/SystemUI/src/com/android/systemui/ChooserPinMigration.kt
deleted file mode 100644
index 2f03259..0000000
--- a/packages/SystemUI/src/com/android/systemui/ChooserPinMigration.kt
+++ /dev/null
@@ -1,140 +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
-
-import android.content.ComponentName
-import android.content.Context
-import android.content.Context.MODE_PRIVATE
-import android.content.Intent
-import android.content.SharedPreferences
-import android.os.Bundle
-import android.os.Environment
-import android.os.storage.StorageManager
-import android.util.Log
-import androidx.core.util.Supplier
-import com.android.internal.R
-import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import java.io.File
-import javax.inject.Inject
-
-/**
- * Performs a migration of pinned targets to the unbundled chooser if legacy data exists.
- *
- * Sends an explicit broadcast with the contents of the legacy pin preferences. The broadcast is
- * protected by the RECEIVE_CHOOSER_PIN_MIGRATION permission. This class requires the
- * ADD_CHOOSER_PINS permission in order to be able to send this broadcast.
- */
-class ChooserPinMigration
-@Inject
-constructor(
-    private val context: Context,
-    private val featureFlags: FeatureFlags,
-    private val broadcastSender: BroadcastSender,
-    legacyPinPrefsFileSupplier: LegacyPinPrefsFileSupplier,
-) : CoreStartable {
-
-    private val legacyPinPrefsFile = legacyPinPrefsFileSupplier.get()
-    private val chooserComponent =
-        ComponentName.unflattenFromString(
-            context.resources.getString(R.string.config_chooserActivity)
-        )
-
-    override fun start() {
-        if (migrationIsRequired()) {
-            doMigration()
-        }
-    }
-
-    private fun migrationIsRequired(): Boolean {
-        return featureFlags.isEnabled(Flags.CHOOSER_MIGRATION_ENABLED) &&
-            legacyPinPrefsFile.exists() &&
-            chooserComponent?.packageName != null
-    }
-
-    private fun doMigration() {
-        Log.i(TAG, "Beginning migration")
-
-        val legacyPinPrefs = context.getSharedPreferences(legacyPinPrefsFile, MODE_PRIVATE)
-
-        if (legacyPinPrefs.all.isEmpty()) {
-            Log.i(TAG, "No data to migrate, deleting legacy file")
-        } else {
-            sendSharedPreferences(legacyPinPrefs)
-            Log.i(TAG, "Legacy data sent, deleting legacy preferences")
-
-            val legacyPinPrefsEditor = legacyPinPrefs.edit()
-            legacyPinPrefsEditor.clear()
-            if (!legacyPinPrefsEditor.commit()) {
-                Log.e(TAG, "Failed to delete legacy preferences")
-                return
-            }
-        }
-
-        if (!legacyPinPrefsFile.delete()) {
-            Log.e(TAG, "Legacy preferences deleted, but failed to delete legacy preferences file")
-            return
-        }
-
-        Log.i(TAG, "Legacy preference deletion complete")
-    }
-
-    private fun sendSharedPreferences(sharedPreferences: SharedPreferences) {
-        val bundle = Bundle()
-
-        sharedPreferences.all.entries.forEach { (key, value) ->
-            when (value) {
-                is Boolean -> bundle.putBoolean(key, value)
-                else -> Log.e(TAG, "Unsupported preference type for $key: ${value?.javaClass}")
-            }
-        }
-
-        sendBundle(bundle)
-    }
-
-    private fun sendBundle(bundle: Bundle) {
-        val intent =
-            Intent().apply {
-                `package` = chooserComponent?.packageName!!
-                action = BROADCAST_ACTION
-                putExtras(bundle)
-            }
-        broadcastSender.sendBroadcast(intent, BROADCAST_PERMISSION)
-    }
-
-    companion object {
-        private const val TAG = "PinnedShareTargetMigration"
-        private const val BROADCAST_ACTION = "android.intent.action.CHOOSER_PIN_MIGRATION"
-        private const val BROADCAST_PERMISSION = "android.permission.RECEIVE_CHOOSER_PIN_MIGRATION"
-
-        class LegacyPinPrefsFileSupplier @Inject constructor(private val context: Context) :
-            Supplier<File> {
-
-            override fun get(): File {
-                val packageDirectory =
-                    Environment.getDataUserCePackageDirectory(
-                        StorageManager.UUID_PRIVATE_INTERNAL,
-                        context.userId,
-                        context.packageName,
-                    )
-                val sharedPrefsDirectory = File(packageDirectory, "shared_prefs")
-                return File(sharedPrefsDirectory, "chooser_pin_settings.xml")
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index 179eb39..a3e7d71 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -35,6 +35,7 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.settingslib.Utils
 import com.android.systemui.animation.Interpolators
+import com.android.systemui.biometrics.AuthController
 import com.android.systemui.log.ScreenDecorationsLogger
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.util.asIndenting
@@ -52,6 +53,7 @@
     val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     val mainExecutor: Executor,
     val logger: ScreenDecorationsLogger,
+    val authController: AuthController,
 ) : ScreenDecorations.DisplayCutoutView(context, pos) {
     private var showScanningAnim = false
     private val rimPaint = Paint()
@@ -102,7 +104,9 @@
     }
 
     override fun enableShowProtection(show: Boolean) {
-        val showScanningAnimNow = keyguardUpdateMonitor.isFaceDetectionRunning && show
+        val animationRequired =
+                keyguardUpdateMonitor.isFaceDetectionRunning || authController.isShowing
+        val showScanningAnimNow = animationRequired && show
         if (showScanningAnimNow == showScanningAnim) {
             return
         }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 12b5705..f3c71da 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -210,8 +210,8 @@
         // Saving in instance variable since to prevent GC since
         // NotificationShadeWindowController.registerCallback() only keeps weak references.
         mNotificationShadeCallback =
-                (keyguardShowing, keyguardOccluded, bouncerShowing, mDozing, panelExpanded,
-                            isDreaming) ->
+                (keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, mDozing,
+                        panelExpanded, isDreaming) ->
                         registerOrUnregisterDismissNotificationShadeAction();
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 92344db..0999229 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -1005,9 +1005,11 @@
      * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
      */
     public boolean isRearFpsSupported() {
-        for (FingerprintSensorPropertiesInternal prop: mFpProps) {
-            if (prop.sensorType == TYPE_REAR) {
-                return true;
+        if (mFpProps != null) {
+            for (FingerprintSensorPropertiesInternal prop: mFpProps) {
+                if (prop.sensorType == TYPE_REAR) {
+                    return true;
+                }
             }
         }
         return false;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index ac30311..aabdafb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -563,6 +563,7 @@
                 (TouchProcessorResult.ProcessedTouch) result;
         final NormalizedTouchData data = processedTouch.getTouchData();
 
+        boolean shouldPilfer = false;
         mActivePointerId = processedTouch.getPointerOnSensorId();
         switch (processedTouch.getEvent()) {
             case DOWN:
@@ -581,8 +582,7 @@
                         mStatusBarStateController.isDozing());
 
                 // Pilfer if valid overlap, don't allow following events to reach keyguard
-                mInputManager.pilferPointers(
-                        mOverlay.getOverlayView().getViewRootImpl().getInputToken());
+                shouldPilfer = true;
                 break;
 
             case UP:
@@ -621,6 +621,12 @@
         // Always pilfer pointers that are within sensor area or when alternate bouncer is showing
         if (isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), true)
                 || mAlternateBouncerInteractor.isVisibleState()) {
+            shouldPilfer = true;
+        }
+
+        // Execute the pilfer, never pilfer if a vertical swipe is in progress
+        if (shouldPilfer && mLockscreenShadeTransitionController.getQSDragProgress() == 0f
+                && !mPrimaryBouncerInteractor.isInTransit()) {
             mInputManager.pilferPointers(
                     mOverlay.getOverlayView().getViewRootImpl().getInputToken());
         }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index f6b7133..691017b 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -324,10 +324,6 @@
 
     @Override
     public boolean isFalseLongTap(@Penalty int penalty) {
-        if (!mFeatureFlags.isEnabled(Flags.FALSING_FOR_LONG_TAPS)) {
-            return false;
-        }
-
         checkDestroyed();
 
         if (skipFalsing(GENERIC)) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index edda8752..63b4288 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -21,7 +21,6 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN;
-import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
 
 import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
 
@@ -36,7 +35,6 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlags;
 
 import javax.inject.Inject;
 import javax.inject.Provider;
@@ -59,7 +57,6 @@
     private final Provider<ClipboardOverlayController> mOverlayProvider;
     private final ClipboardToast mClipboardToast;
     private final ClipboardManager mClipboardManager;
-    private final FeatureFlags mFeatureFlags;
     private final UiEventLogger mUiEventLogger;
     private ClipboardOverlay mClipboardOverlay;
 
@@ -68,13 +65,11 @@
             Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
             ClipboardToast clipboardToast,
             ClipboardManager clipboardManager,
-            FeatureFlags featureFlags,
             UiEventLogger uiEventLogger) {
         mContext = context;
         mOverlayProvider = clipboardOverlayControllerProvider;
         mClipboardToast = clipboardToast;
         mClipboardManager = clipboardManager;
-        mFeatureFlags = featureFlags;
         mUiEventLogger = uiEventLogger;
     }
 
@@ -113,11 +108,7 @@
         } else {
             mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource);
         }
-        if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
-            mClipboardOverlay.setClipData(clipData, clipSource);
-        } else {
-            mClipboardOverlay.setClipDataLegacy(clipData, clipSource);
-        }
+        mClipboardOverlay.setClipData(clipData, clipSource);
         mClipboardOverlay.setOnSessionCompleteListener(() -> {
             // Session is complete, free memory until it's needed again.
             mClipboardOverlay = null;
@@ -160,8 +151,6 @@
     }
 
     interface ClipboardOverlay {
-        void setClipDataLegacy(ClipData clipData, String clipSource);
-
         void setClipData(ClipData clipData, String clipSource);
 
         void setOnSessionCompleteListener(Runnable runnable);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index e6affb0..5230159 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -32,7 +32,6 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
-import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
 import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR;
 
 import android.animation.Animator;
@@ -40,20 +39,14 @@
 import android.app.RemoteAction;
 import android.content.BroadcastReceiver;
 import android.content.ClipData;
-import android.content.ClipDescription;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
 import android.hardware.input.InputManager;
 import android.net.Uri;
 import android.os.Looper;
 import android.provider.DeviceConfig;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Size;
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.InputMonitor;
@@ -72,7 +65,6 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.screenshot.TimeoutHandler;
 
-import java.io.IOException;
 import java.util.Optional;
 import java.util.concurrent.Executor;
 
@@ -170,9 +162,7 @@
 
                 @Override
                 public void onMinimizedViewTapped() {
-                    if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
-                        animateFromMinimized();
-                    }
+                    animateFromMinimized();
                 }
             };
 
@@ -255,11 +245,9 @@
     @VisibleForTesting
     void onInsetsChanged(WindowInsets insets, int orientation) {
         mView.setInsets(insets, orientation);
-        if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
-            if (shouldShowMinimized(insets) && !mIsMinimized) {
-                mIsMinimized = true;
-                mView.setMinimized(true);
-            }
+        if (shouldShowMinimized(insets) && !mIsMinimized) {
+            mIsMinimized = true;
+            mView.setMinimized(true);
         }
     }
 
@@ -401,61 +389,6 @@
         });
     }
 
-    @Override // ClipboardListener.ClipboardOverlay
-    public void setClipDataLegacy(ClipData clipData, String clipSource) {
-        if (mExitAnimator != null && mExitAnimator.isRunning()) {
-            mExitAnimator.cancel();
-        }
-        reset();
-        mClipboardLogger.setClipSource(clipSource);
-        String accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
-
-        boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
-                && clipData.getDescription().getExtras()
-                .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
-        boolean isRemote = mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR)
-                && mClipboardUtils.isRemoteCopy(mContext, clipData, clipSource);
-        if (clipData == null || clipData.getItemCount() == 0) {
-            mView.showDefaultTextPreview();
-        } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
-            ClipData.Item item = clipData.getItemAt(0);
-            if (isRemote || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                    CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
-                if (item.getTextLinks() != null) {
-                    classifyText(clipData.getItemAt(0), clipSource);
-                }
-            }
-            if (isSensitive) {
-                showEditableText(mContext.getString(R.string.clipboard_asterisks), true);
-            } else {
-                showEditableText(item.getText(), false);
-            }
-            mOnShareTapped = () -> shareContent(clipData);
-            mView.showShareChip();
-            accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied);
-        } else if (clipData.getItemAt(0).getUri() != null) {
-            if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) {
-                accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied);
-            }
-            mOnShareTapped = () -> shareContent(clipData);
-            mView.showShareChip();
-        } else {
-            mView.showDefaultTextPreview();
-        }
-        if (!isRemote) {
-            maybeShowRemoteCopy(clipData);
-        }
-        animateIn();
-        mView.announceForAccessibility(accessibilityAnnouncement);
-        if (isRemote) {
-            mTimeoutHandler.cancelTimeout();
-            mOnUiUpdate = null;
-        } else {
-            mOnUiUpdate = mTimeoutHandler::resetTimeout;
-            mOnUiUpdate.run();
-        }
-    }
-
     private void maybeShowRemoteCopy(ClipData clipData) {
         Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext);
         // Only show remote copy if it's available.
@@ -478,22 +411,6 @@
         mOnSessionCompleteListener = runnable;
     }
 
-    private void classifyText(ClipData.Item item, String source) {
-        mBgExecutor.execute(() -> {
-            Optional<RemoteAction> action = mClipboardUtils.getAction(item, source);
-            mView.post(() -> {
-                mView.resetActionChips();
-                action.ifPresent(remoteAction -> {
-                    mView.setActionChip(remoteAction, () -> {
-                        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
-                        animateOut();
-                    });
-                    mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
-                });
-            });
-        });
-    }
-
     private void monitorOutsideTouches() {
         InputManager inputManager = mContext.getSystemService(InputManager.class);
         mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0);
@@ -534,43 +451,6 @@
         animateOut();
     }
 
-    private void showEditableText(CharSequence text, boolean hidden) {
-        mView.showTextPreview(text.toString(), hidden);
-        mView.setEditAccessibilityAction(true);
-        mOnPreviewTapped = this::editText;
-    }
-
-    private boolean tryShowEditableImage(Uri uri, boolean isSensitive) {
-        Runnable listener = () -> editImage(uri);
-        ContentResolver resolver = mContext.getContentResolver();
-        String mimeType = resolver.getType(uri);
-        boolean isEditableImage = mimeType != null && mimeType.startsWith("image");
-        if (isSensitive) {
-            mView.showImagePreview(null);
-            if (isEditableImage) {
-                mOnPreviewTapped = listener;
-                mView.setEditAccessibilityAction(true);
-            }
-        } else if (isEditableImage) { // if the MIMEtype is image, try to load
-            try {
-                int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
-                // The width of the view is capped, height maintains aspect ratio, so allow it to be
-                // taller if needed.
-                Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
-                mView.showImagePreview(thumbnail);
-                mView.setEditAccessibilityAction(true);
-                mOnPreviewTapped = listener;
-            } catch (IOException e) {
-                Log.e(TAG, "Thumbnail loading failed", e);
-                mView.showDefaultTextPreview();
-                isEditableImage = false;
-            }
-        } else {
-            mView.showDefaultTextPreview();
-        }
-        return isEditableImage;
-    }
-
     private void animateIn() {
         if (mEnterAnimator != null && mEnterAnimator.isRunning()) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
index 25caaea..758a656 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
@@ -88,25 +88,4 @@
         }
         return actions;
     }
-
-    public Optional<RemoteAction> getAction(ClipData.Item item, String source) {
-        return getActions(item).stream().filter(remoteAction -> {
-            ComponentName component = remoteAction.getActionIntent().getIntent().getComponent();
-            return component != null && !TextUtils.equals(source, component.getPackageName());
-        }).findFirst();
-    }
-
-    private ArrayList<RemoteAction> getActions(ClipData.Item item) {
-        ArrayList<RemoteAction> actions = new ArrayList<>();
-        for (TextLinks.TextLink link : item.getTextLinks().getLinks()) {
-            // skip classification for incidental entities
-            if (link.getEnd() - link.getStart()
-                    >= item.getText().length() * MINIMUM_ENTITY_PROPORTION) {
-                TextClassification classification = mTextClassifier.classifyText(
-                        item.getText(), link.getStart(), link.getEnd(), null);
-                actions.addAll(classification.getActions());
-            }
-        }
-        return actions;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt
new file mode 100644
index 0000000..81ed076
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.ui.view
+
+import android.util.MathUtils
+import android.view.MotionEvent
+
+/**
+ * Returns the distance from the raw position of this [MotionEvent] and the given coordinates.
+ * Because this is all expected to be in the coordinate space of the display and not the view,
+ * applying mutations to the view (such as scaling animations) does not affect the distance
+ * measured.
+ * @param xOnDisplay the x coordinate relative to the display
+ * @param yOnDisplay the y coordinate relative to the display
+ * @return distance from the raw position of this [MotionEvent] and the given coordinates
+ */
+fun MotionEvent.rawDistanceFrom(
+    xOnDisplay: Float,
+    yOnDisplay: Float,
+): Float {
+    return MathUtils.dist(this.rawX, this.rawY, xOnDisplay, yOnDisplay)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index df236e7..9bf6b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.dagger
 
 import com.android.keyguard.KeyguardBiometricLockoutLogger
-import com.android.systemui.ChooserPinMigration
 import com.android.systemui.ChooserSelector
 import com.android.systemui.CoreStartable
 import com.android.systemui.LatencyTester
@@ -76,13 +75,6 @@
     @ClassKey(AuthController::class)
     abstract fun bindAuthController(service: AuthController): CoreStartable
 
-    /** Inject into ChooserPinMigration. */
-    @Binds
-    @IntoMap
-    @ClassKey(ChooserPinMigration::class)
-    @PerUser
-    abstract fun bindChooserPinMigration(sysui: ChooserPinMigration): CoreStartable
-
     /** Inject into ChooserCoreStartable. */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 3b38870..b48885f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -85,6 +85,8 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
 import com.android.systemui.statusbar.notification.people.PeopleHubModule;
 import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
@@ -315,4 +317,11 @@
     @Binds
     abstract LargeScreenShadeInterpolator largeScreensShadeInterpolator(
             LargeScreenShadeInterpolatorImpl impl);
+
+    @SysUISingleton
+    @Provides
+    static VisualInterruptionDecisionProvider provideVisualInterruptionDecisionProvider(
+            NotificationInterruptStateProvider innerProvider) {
+        return new NotificationInterruptStateProviderWrapper(innerProvider);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 88c0c50..4e62104 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -98,7 +98,8 @@
     }
 
     fun shouldShowFaceScanningAnim(): Boolean {
-        return canShowFaceScanningAnim() && keyguardUpdateMonitor.isFaceDetectionRunning
+        return canShowFaceScanningAnim() &&
+                (keyguardUpdateMonitor.isFaceDetectionRunning || authController.isShowing)
     }
 }
 
@@ -142,6 +143,7 @@
                 keyguardUpdateMonitor,
                 mainExecutor,
                 logger,
+                authController,
         )
         view.id = viewId
         view.setColor(tintColor)
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 2ea7bce..570132e1 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -303,10 +303,6 @@
                 }
 
                 flingToExpansion(verticalVelocity, expansion);
-
-                if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) {
-                    mCurrentScrimController.reset();
-                }
                 break;
             default:
                 mVelocityTracker.addMovement(motionEvent);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 60a3326c..e389f5b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -65,7 +65,7 @@
     val FSI_ON_DND_UPDATE = releasedFlag(259130119, "fsi_on_dnd_update")
 
     // TODO(b/254512538): Tracking Bug
-    val INSTANT_VOICE_REPLY = releasedFlag(111, "instant_voice_reply")
+    val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply")
 
     // TODO(b/254512425): Tracking Bug
     val NOTIFICATION_MEMORY_MONITOR_ENABLED =
@@ -103,6 +103,11 @@
     val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
         releasedFlag(254647461, "filter_unseen_notifs_on_keyguard")
 
+    // TODO(b/277338665): Tracking Bug
+    @JvmField
+    val NOTIFICATION_SHELF_REFACTOR =
+        unreleasedFlag(271161129, "notification_shelf_refactor")
+
     // TODO(b/263414400): Tracking Bug
     @JvmField
     val NOTIFICATION_ANIMATE_BIG_PICTURE =
@@ -132,6 +137,11 @@
     // TODO(b/254512676): Tracking Bug
     @JvmField val LOCKSCREEN_CUSTOM_CLOCKS = releasedFlag(207, "lockscreen_custom_clocks")
 
+    // TODO(b/275694445): Tracking Bug
+    @JvmField
+    val LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING = unreleasedFlag(208,
+        "lockscreen_without_secure_lock_when_dreaming")
+
     /**
      * Whether the clock on a wide lock screen should use the new "stepping" animation for moving
      * the digits when the clock moves.
@@ -224,12 +234,18 @@
     /** Whether to inflate the bouncer view on a background thread. */
     // TODO(b/273341787): Tracking Bug
     @JvmField
-    val PREVENT_BYPASS_KEYGUARD = releasedFlag(230, "prevent_bypass_keyguard")
+    val PREVENT_BYPASS_KEYGUARD = unreleasedFlag(230, "prevent_bypass_keyguard", teamfood = true)
 
     /** Whether to use a new data source for intents to run on keyguard dismissal. */
     @JvmField
     val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag(231, "refactor_keyguard_dismiss_intent")
 
+    /** Whether to allow long-press on the lock screen to directly open wallpaper picker. */
+    // TODO(b/277220285): Tracking bug.
+    @JvmField
+    val LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP =
+        unreleasedFlag(232, "lock_screen_long_press_directly_opens_wallpaper_picker")
+
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
     @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -395,7 +411,7 @@
     val MEDIA_RETAIN_RECOMMENDATIONS = releasedFlag(916, "media_retain_recommendations")
 
     // TODO(b/270437894): Tracking Bug
-    val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume")
+    val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume", teamfood = true)
 
     // 1000 - dock
     val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
@@ -403,9 +419,6 @@
     // TODO(b/254512758): Tracking Bug
     @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
 
-    // TODO(b/270882464): Tracking Bug
-    val ENABLE_DOCK_SETUP_V2 = releasedFlag(1005, "enable_dock_setup_v2")
-
     // TODO(b/265045965): Tracking Bug
     val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
 
@@ -605,13 +618,8 @@
     val SHARESHEET_SCROLLABLE_IMAGE_PREVIEW =
         releasedFlag(1504, "sharesheet_scrollable_image_preview")
 
-    // TODO(b/274137694) Tracking Bug
-    val CHOOSER_MIGRATION_ENABLED = unreleasedFlag(1505, "chooser_migration_enabled")
-
     // 1700 - clipboard
     @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
-    // TODO(b/267162944): Tracking bug
-    @JvmField val CLIPBOARD_MINIMIZED_LAYOUT = releasedFlag(1702, "clipboard_data_model")
 
     // 1800 - shade container
     @JvmField
@@ -639,9 +647,6 @@
     val APP_PANELS_REMOVE_APPS_ALLOWED =
         unreleasedFlag(2003, "app_panels_remove_apps_allowed", teamfood = true)
 
-    // 2100 - Falsing Manager
-    @JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps")
-
     // 2200 - udfps
     // TODO(b/259264861): Tracking Bug
     @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag(2200, "udfps_new_touch_detection")
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index a173f8b..2ef5e19 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -90,9 +90,9 @@
 
     private fun updateResources() {
         context.resources.apply {
-            filledRectangleColor = getColor(R.color.backlight_indicator_step_filled)
-            emptyRectangleColor = getColor(R.color.backlight_indicator_step_empty)
-            backgroundColor = getColor(R.color.backlight_indicator_background)
+            filledRectangleColor = getColor(R.color.backlight_indicator_step_filled, context.theme)
+            emptyRectangleColor = getColor(R.color.backlight_indicator_step_empty, context.theme)
+            backgroundColor = getColor(R.color.backlight_indicator_background, context.theme)
             rootProperties =
                 RootProperties(
                     cornerRadius =
@@ -224,7 +224,6 @@
 
     private fun setWindowTitle() {
         val attrs = window.attributes
-        // TODO(b/271796169): check if title needs to be a translatable resource.
         attrs.title = "KeyboardBacklightDialog"
         attrs?.y = dialogBottomMargin
         window.attributes = attrs
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 8a7f322..cf95f81 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -128,6 +128,8 @@
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -1185,6 +1187,8 @@
     private Lazy<ScrimController> mScrimControllerLazy;
     private IActivityTaskManager mActivityTaskManagerService;
 
+    private FeatureFlags mFeatureFlags;
+
     /**
      * Injected constructor. See {@link KeyguardModule}.
      */
@@ -1216,7 +1220,8 @@
             Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
             Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
             Lazy<ScrimController> scrimControllerLazy,
-            IActivityTaskManager activityTaskManagerService) {
+            IActivityTaskManager activityTaskManagerService,
+            FeatureFlags featureFlags) {
         mContext = context;
         mUserTracker = userTracker;
         mFalsingCollector = falsingCollector;
@@ -1272,6 +1277,8 @@
 
         mDreamOpenAnimationDuration = (int) DREAMING_ANIMATION_DURATION_MS;
         mDreamCloseAnimationDuration = (int) LOCKSCREEN_ANIMATION_DURATION_MS;
+
+        mFeatureFlags = featureFlags;
     }
 
     public void userActivity() {
@@ -1685,14 +1692,17 @@
     }
 
     /**
-     * A dream started.  We should lock after the usual screen-off lock timeout but only
-     * if there is a secure lock pattern.
+     * A dream started. We should lock after the usual screen-off lock timeout regardless if
+     * there is a secure lock pattern or not
      */
     public void onDreamingStarted() {
         mUpdateMonitor.dispatchDreamingStarted();
         synchronized (this) {
+            final boolean alwaysShowKeyguard =
+                mFeatureFlags.isEnabled(Flags.LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING);
             if (mDeviceInteractive
-                    && mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) {
+                && (alwaysShowKeyguard ||
+                mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser()))) {
                 doKeyguardLaterLocked();
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 8764f12..fd47104 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -40,6 +40,7 @@
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -121,7 +122,8 @@
             Lazy<NotificationShadeWindowController> notificationShadeWindowController,
             Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
             Lazy<ScrimController> scrimControllerLazy,
-            IActivityTaskManager activityTaskManagerService) {
+            IActivityTaskManager activityTaskManagerService,
+            FeatureFlags featureFlags) {
         return new KeyguardViewMediator(
                 context,
                 userTracker,
@@ -152,7 +154,8 @@
                 notificationShadeWindowController,
                 activityLaunchAnimator,
                 scrimControllerLazy,
-                activityTaskManagerService);
+                activityTaskManagerService,
+                featureFlags);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 56e7398..0abce82 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -92,6 +92,9 @@
     /** Current state of whether face authentication is running. */
     val isAuthRunning: Flow<Boolean>
 
+    /** Whether bypass is currently enabled */
+    val isBypassEnabled: Flow<Boolean>
+
     /**
      * Trigger face authentication.
      *
@@ -166,7 +169,7 @@
     override val isAuthenticated: Flow<Boolean>
         get() = _isAuthenticated
 
-    private val bypassEnabled: Flow<Boolean> =
+    override val isBypassEnabled: Flow<Boolean> =
         keyguardBypassController?.let {
             conflatedCallbackFlow {
                 val callback =
@@ -222,7 +225,7 @@
         // & detection is supported & biometric unlock is not allowed.
         listOf(
                 canFaceAuthOrDetectRun(),
-                logAndObserve(bypassEnabled, "bypassEnabled"),
+                logAndObserve(isBypassEnabled, "isBypassEnabled"),
                 logAndObserve(
                     biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(),
                     "nonStrongBiometricIsNotAllowed"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index 8ece318..ab4abbf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -19,6 +19,7 @@
 
 import android.content.Context
 import android.os.UserHandle
+import android.util.LayoutDirection
 import com.android.systemui.Dumpable
 import com.android.systemui.R
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -113,30 +114,6 @@
                 initialValue = emptyMap(),
             )
 
-    private val _slotPickerRepresentations: List<KeyguardSlotPickerRepresentation> by lazy {
-        fun parseSlot(unparsedSlot: String): Pair<String, Int> {
-            val split = unparsedSlot.split(SLOT_CONFIG_DELIMITER)
-            check(split.size == 2)
-            val slotId = split[0]
-            val slotCapacity = split[1].toInt()
-            return slotId to slotCapacity
-        }
-
-        val unparsedSlots =
-            appContext.resources.getStringArray(R.array.config_keyguardQuickAffordanceSlots)
-
-        val seenSlotIds = mutableSetOf<String>()
-        unparsedSlots.mapNotNull { unparsedSlot ->
-            val (slotId, slotCapacity) = parseSlot(unparsedSlot)
-            check(!seenSlotIds.contains(slotId)) { "Duplicate slot \"$slotId\"!" }
-            seenSlotIds.add(slotId)
-            KeyguardSlotPickerRepresentation(
-                id = slotId,
-                maxSelectedAffordances = slotCapacity,
-            )
-        }
-    }
-
     init {
         legacySettingSyncer.startSyncing()
         dumpManager.registerDumpable("KeyguardQuickAffordances", Dumpster())
@@ -211,7 +188,30 @@
      * each slot and select which affordance(s) is/are installed in each slot on the keyguard.
      */
     fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
-        return _slotPickerRepresentations
+        fun parseSlot(unparsedSlot: String): Pair<String, Int> {
+            val split = unparsedSlot.split(SLOT_CONFIG_DELIMITER)
+            check(split.size == 2)
+            val slotId = split[0]
+            val slotCapacity = split[1].toInt()
+            return slotId to slotCapacity
+        }
+
+        val unparsedSlots =
+            appContext.resources.getStringArray(R.array.config_keyguardQuickAffordanceSlots)
+        if (appContext.resources.configuration.layoutDirection == LayoutDirection.RTL) {
+            unparsedSlots.reverse()
+        }
+
+        val seenSlotIds = mutableSetOf<String>()
+        return unparsedSlots.mapNotNull { unparsedSlot ->
+            val (slotId, slotCapacity) = parseSlot(unparsedSlot)
+            check(!seenSlotIds.contains(slotId)) { "Duplicate slot \"$slotId\"!" }
+            seenSlotIds.add(slotId)
+            KeyguardSlotPickerRepresentation(
+                id = slotId,
+                maxSelectedAffordances = slotCapacity,
+            )
+        }
     }
 
     private inner class Dumpster : Dumpable {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
index 6525a13..ea6700e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
@@ -17,29 +17,29 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.view.accessibility.AccessibilityManager
+import androidx.annotation.VisibleForTesting
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
-import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.shared.model.Position
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.keyguard.domain.model.KeyguardSettingsPopupMenuModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
@@ -47,6 +47,7 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /** Business logic for use-cases related to the keyguard long-press feature. */
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -54,18 +55,16 @@
 class KeyguardLongPressInteractor
 @Inject
 constructor(
-    @Application unsafeContext: Context,
-    @Application scope: CoroutineScope,
+    @Application private val scope: CoroutineScope,
     transitionInteractor: KeyguardTransitionInteractor,
     repository: KeyguardRepository,
-    private val activityStarter: ActivityStarter,
     private val logger: UiEventLogger,
     private val featureFlags: FeatureFlags,
     broadcastDispatcher: BroadcastDispatcher,
+    private val accessibilityManager: AccessibilityManagerWrapper,
 ) {
-    private val appContext = unsafeContext.applicationContext
-
-    private val _isLongPressHandlingEnabled: StateFlow<Boolean> =
+    /** Whether the long-press handling feature should be enabled. */
+    val isLongPressHandlingEnabled: StateFlow<Boolean> =
         if (isFeatureEnabled()) {
                 combine(
                     transitionInteractor.finishedKeyguardState.map {
@@ -84,19 +83,35 @@
                 initialValue = false,
             )
 
-    /** Whether the long-press handling feature should be enabled. */
-    val isLongPressHandlingEnabled: Flow<Boolean> = _isLongPressHandlingEnabled
-
-    private val _menu = MutableStateFlow<KeyguardSettingsPopupMenuModel?>(null)
-    /** Model for a menu that should be shown; `null` when no menu should be shown. */
-    val menu: Flow<KeyguardSettingsPopupMenuModel?> =
-        isLongPressHandlingEnabled.flatMapLatest { isEnabled ->
-            if (isEnabled) {
-                _menu
-            } else {
-                flowOf(null)
+    private val _isMenuVisible = MutableStateFlow(false)
+    /** Model for whether the menu should be shown. */
+    val isMenuVisible: StateFlow<Boolean> =
+        isLongPressHandlingEnabled
+            .flatMapLatest { isEnabled ->
+                if (isEnabled) {
+                    _isMenuVisible.asStateFlow()
+                } else {
+                    // Reset the state so we don't see a menu when long-press handling is enabled
+                    // again in the future.
+                    _isMenuVisible.value = false
+                    flowOf(false)
+                }
             }
-        }
+            .stateIn(
+                scope = scope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    private val _shouldOpenSettings = MutableStateFlow(false)
+    /**
+     * Whether the long-press accessible "settings" flow should be opened.
+     *
+     * Note that [onSettingsShown] must be invoked to consume this, once the settings are opened.
+     */
+    val shouldOpenSettings = _shouldOpenSettings.asStateFlow()
+
+    private var delayedHideMenuJob: Job? = null
 
     init {
         if (isFeatureEnabled()) {
@@ -110,15 +125,46 @@
     }
 
     /** Notifies that the user has long-pressed on the lock screen. */
-    fun onLongPress(x: Int, y: Int) {
-        if (!_isLongPressHandlingEnabled.value) {
+    fun onLongPress() {
+        if (!isLongPressHandlingEnabled.value) {
             return
         }
 
-        showMenu(
-            x = x,
-            y = y,
-        )
+        if (featureFlags.isEnabled(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP)) {
+            showSettings()
+        } else {
+            showMenu()
+        }
+    }
+
+    /** Notifies that the user has touched outside of the pop-up. */
+    fun onTouchedOutside() {
+        hideMenu()
+    }
+
+    /** Notifies that the user has started a touch gesture on the menu. */
+    fun onMenuTouchGestureStarted() {
+        cancelAutomaticMenuHiding()
+    }
+
+    /** Notifies that the user has started a touch gesture on the menu. */
+    fun onMenuTouchGestureEnded(isClick: Boolean) {
+        if (isClick) {
+            hideMenu()
+            logger.log(LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED)
+            showSettings()
+        } else {
+            scheduleAutomaticMenuHiding()
+        }
+    }
+
+    /** Notifies that the settings UI has been shown, consuming the event to show it. */
+    fun onSettingsShown() {
+        _shouldOpenSettings.value = false
+    }
+
+    private fun showSettings() {
+        _shouldOpenSettings.value = true
     }
 
     private fun isFeatureEnabled(): Boolean {
@@ -126,51 +172,40 @@
             featureFlags.isEnabled(Flags.REVAMPED_WALLPAPER_UI)
     }
 
-    /** Updates application state to ask to show the menu at the given coordinates. */
-    private fun showMenu(
-        x: Int,
-        y: Int,
-    ) {
-        _menu.value =
-            KeyguardSettingsPopupMenuModel(
-                position =
-                    Position(
-                        x = x,
-                        y = y,
-                    ),
-                onClicked = {
-                    hideMenu()
-                    navigateToLockScreenSettings()
-                },
-                onDismissed = { hideMenu() },
-            )
+    /** Updates application state to ask to show the menu. */
+    private fun showMenu() {
+        _isMenuVisible.value = true
+        scheduleAutomaticMenuHiding()
         logger.log(LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN)
     }
 
+    private fun scheduleAutomaticMenuHiding() {
+        cancelAutomaticMenuHiding()
+        delayedHideMenuJob =
+            scope.launch {
+                delay(timeOutMs())
+                hideMenu()
+            }
+    }
+
     /** Updates application state to ask to hide the menu. */
     private fun hideMenu() {
-        _menu.value = null
+        cancelAutomaticMenuHiding()
+        _isMenuVisible.value = false
     }
 
-    /** Opens the wallpaper picker screen after the device is unlocked by the user. */
-    private fun navigateToLockScreenSettings() {
-        logger.log(LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED)
-        activityStarter.dismissKeyguardThenExecute(
-            /* action= */ {
-                appContext.startActivity(
-                    Intent(Intent.ACTION_SET_WALLPAPER).apply {
-                        flags = Intent.FLAG_ACTIVITY_NEW_TASK
-                        appContext
-                            .getString(R.string.config_wallpaperPickerPackage)
-                            .takeIf { it.isNotEmpty() }
-                            ?.let { packageName -> setPackage(packageName) }
-                    }
-                )
-                true
-            },
-            /* cancel= */ {},
-            /* afterKeyguardGone= */ true,
-        )
+    private fun cancelAutomaticMenuHiding() {
+        delayedHideMenuJob?.cancel()
+        delayedHideMenuJob = null
+    }
+
+    private fun timeOutMs(): Long {
+        return accessibilityManager
+            .getRecommendedTimeoutMillis(
+                DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS.toInt(),
+                AccessibilityManager.FLAG_CONTENT_ICONS or AccessibilityManager.FLAG_CONTENT_TEXT,
+            )
+            .toLong()
     }
 
     enum class LogEvents(
@@ -184,4 +219,8 @@
 
         override fun getId() = _id
     }
+
+    companion object {
+        @VisibleForTesting const val DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS = 5000L
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardSettingsPopupMenuModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardSettingsPopupMenuModel.kt
deleted file mode 100644
index 7c61e71..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardSettingsPopupMenuModel.kt
+++ /dev/null
@@ -1,30 +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.keyguard.domain.model
-
-import com.android.systemui.common.shared.model.Position
-
-/** Models a settings popup menu for the lock screen. */
-data class KeyguardSettingsPopupMenuModel(
-    /** Where the menu should be anchored, roughly in screen space. */
-    val position: Position,
-    /** Callback to invoke when the menu gets clicked by the user. */
-    val onClicked: () -> Unit,
-    /** Callback to invoke when the menu gets dismissed by the user. */
-    val onDismissed: () -> Unit,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt
new file mode 100644
index 0000000..568db2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.os.VibrationEffect
+import kotlin.time.Duration.Companion.milliseconds
+
+object KeyguardBottomAreaVibrations {
+
+    val ShakeAnimationDuration = 300.milliseconds
+    const val ShakeAnimationCycles = 5f
+
+    private const val SmallVibrationScale = 0.3f
+    private const val BigVibrationScale = 0.6f
+
+    val Shake =
+        VibrationEffect.startComposition()
+            .apply {
+                val vibrationDelayMs =
+                    (ShakeAnimationDuration.inWholeMilliseconds / ShakeAnimationCycles * 2).toInt()
+                val vibrationCount = ShakeAnimationCycles.toInt() * 2
+                repeat(vibrationCount) {
+                    addPrimitive(
+                        VibrationEffect.Composition.PRIMITIVE_TICK,
+                        SmallVibrationScale,
+                        vibrationDelayMs,
+                    )
+                }
+            }
+            .compose()
+
+    val Activated =
+        VibrationEffect.startComposition()
+            .addPrimitive(
+                VibrationEffect.Composition.PRIMITIVE_TICK,
+                BigVibrationScale,
+                0,
+            )
+            .addPrimitive(
+                VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
+                0.1f,
+                0,
+            )
+            .compose()
+
+    val Deactivated =
+        VibrationEffect.startComposition()
+            .addPrimitive(
+                VibrationEffect.Composition.PRIMITIVE_TICK,
+                BigVibrationScale,
+                0,
+            )
+            .addPrimitive(
+                VibrationEffect.Composition.PRIMITIVE_QUICK_FALL,
+                0.1f,
+                0,
+            )
+            .compose()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index d63636c..68ac7e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -17,41 +17,42 @@
 package com.android.systemui.keyguard.ui.binder
 
 import android.annotation.SuppressLint
+import android.content.Intent
+import android.graphics.Rect
 import android.graphics.drawable.Animatable2
-import android.os.VibrationEffect
 import android.util.Size
 import android.util.TypedValue
-import android.view.MotionEvent
 import android.view.View
-import android.view.ViewConfiguration
 import android.view.ViewGroup
 import android.view.ViewPropertyAnimator
 import android.widget.ImageView
 import android.widget.TextView
-import androidx.core.animation.CycleInterpolator
-import androidx.core.animation.ObjectAnimator
+import androidx.core.view.isInvisible
 import androidx.core.view.isVisible
 import androidx.core.view.updateLayoutParams
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.settingslib.Utils
 import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.Interpolators
+import com.android.systemui.animation.view.LaunchableLinearLayout
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.common.ui.binder.TextViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
-import kotlin.math.pow
-import kotlin.math.sqrt
-import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 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.map
 import kotlinx.coroutines.launch
@@ -91,15 +92,20 @@
          * icon
          */
         fun shouldConstrainToTopOfLockIcon(): Boolean
+
+        /** Destroys this binding, releases resources, and cancels any coroutines. */
+        fun destroy()
     }
 
     /** Binds the view to the view-model, continuing to update the former based on the latter. */
+    @SuppressLint("ClickableViewAccessibility")
     @JvmStatic
     fun bind(
         view: ViewGroup,
         viewModel: KeyguardBottomAreaViewModel,
         falsingManager: FalsingManager?,
         vibratorHelper: VibratorHelper?,
+        activityStarter: ActivityStarter?,
         messageDisplayer: (Int) -> Unit,
     ): Binding {
         val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area)
@@ -110,137 +116,192 @@
         val indicationText: TextView = view.requireViewById(R.id.keyguard_indication_text)
         val indicationTextBottom: TextView =
             view.requireViewById(R.id.keyguard_indication_text_bottom)
+        val settingsMenu: LaunchableLinearLayout =
+            view.requireViewById(R.id.keyguard_settings_button)
 
         view.clipChildren = false
         view.clipToPadding = false
+        view.setOnTouchListener { _, event ->
+            if (settingsMenu.isVisible) {
+                val hitRect = Rect()
+                settingsMenu.getHitRect(hitRect)
+                if (!hitRect.contains(event.x.toInt(), event.y.toInt())) {
+                    viewModel.onTouchedOutsideLockScreenSettingsMenu()
+                }
+            }
+
+            false
+        }
 
         val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
 
-        view.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.STARTED) {
-                launch {
-                    viewModel.startButton.collect { buttonModel ->
-                        updateButton(
+        val disposableHandle =
+            view.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    launch {
+                        viewModel.startButton.collect { buttonModel ->
+                            updateButton(
+                                view = startButton,
+                                viewModel = buttonModel,
+                                falsingManager = falsingManager,
+                                messageDisplayer = messageDisplayer,
+                                vibratorHelper = vibratorHelper,
+                            )
+                        }
+                    }
+
+                    launch {
+                        viewModel.endButton.collect { buttonModel ->
+                            updateButton(
+                                view = endButton,
+                                viewModel = buttonModel,
+                                falsingManager = falsingManager,
+                                messageDisplayer = messageDisplayer,
+                                vibratorHelper = vibratorHelper,
+                            )
+                        }
+                    }
+
+                    launch {
+                        viewModel.isOverlayContainerVisible.collect { isVisible ->
+                            overlayContainer.visibility =
+                                if (isVisible) {
+                                    View.VISIBLE
+                                } else {
+                                    View.INVISIBLE
+                                }
+                        }
+                    }
+
+                    launch {
+                        viewModel.alpha.collect { alpha ->
+                            view.importantForAccessibility =
+                                if (alpha == 0f) {
+                                    View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                } else {
+                                    View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                                }
+
+                            ambientIndicationArea?.alpha = alpha
+                            indicationArea.alpha = alpha
+                        }
+                    }
+
+                    launch {
+                        updateButtonAlpha(
                             view = startButton,
-                            viewModel = buttonModel,
-                            falsingManager = falsingManager,
-                            messageDisplayer = messageDisplayer,
-                            vibratorHelper = vibratorHelper,
+                            viewModel = viewModel.startButton,
+                            alphaFlow = viewModel.alpha,
                         )
                     }
-                }
 
-                launch {
-                    viewModel.endButton.collect { buttonModel ->
-                        updateButton(
+                    launch {
+                        updateButtonAlpha(
                             view = endButton,
-                            viewModel = buttonModel,
-                            falsingManager = falsingManager,
-                            messageDisplayer = messageDisplayer,
-                            vibratorHelper = vibratorHelper,
+                            viewModel = viewModel.endButton,
+                            alphaFlow = viewModel.alpha,
                         )
                     }
-                }
 
-                launch {
-                    viewModel.isOverlayContainerVisible.collect { isVisible ->
-                        overlayContainer.visibility =
+                    launch {
+                        viewModel.indicationAreaTranslationX.collect { translationX ->
+                            indicationArea.translationX = translationX
+                            ambientIndicationArea?.translationX = translationX
+                        }
+                    }
+
+                    launch {
+                        combine(
+                                viewModel.isIndicationAreaPadded,
+                                configurationBasedDimensions.map { it.indicationAreaPaddingPx },
+                            ) { isPadded, paddingIfPaddedPx ->
+                                if (isPadded) {
+                                    paddingIfPaddedPx
+                                } else {
+                                    0
+                                }
+                            }
+                            .collect { paddingPx ->
+                                indicationArea.setPadding(paddingPx, 0, paddingPx, 0)
+                            }
+                    }
+
+                    launch {
+                        configurationBasedDimensions
+                            .map { it.defaultBurnInPreventionYOffsetPx }
+                            .flatMapLatest { defaultBurnInOffsetY ->
+                                viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
+                            }
+                            .collect { translationY ->
+                                indicationArea.translationY = translationY
+                                ambientIndicationArea?.translationY = translationY
+                            }
+                    }
+
+                    launch {
+                        configurationBasedDimensions.collect { dimensions ->
+                            indicationText.setTextSize(
+                                TypedValue.COMPLEX_UNIT_PX,
+                                dimensions.indicationTextSizePx.toFloat(),
+                            )
+                            indicationTextBottom.setTextSize(
+                                TypedValue.COMPLEX_UNIT_PX,
+                                dimensions.indicationTextSizePx.toFloat(),
+                            )
+
+                            startButton.updateLayoutParams<ViewGroup.LayoutParams> {
+                                width = dimensions.buttonSizePx.width
+                                height = dimensions.buttonSizePx.height
+                            }
+                            endButton.updateLayoutParams<ViewGroup.LayoutParams> {
+                                width = dimensions.buttonSizePx.width
+                                height = dimensions.buttonSizePx.height
+                            }
+                        }
+                    }
+
+                    launch {
+                        viewModel.settingsMenuViewModel.isVisible.distinctUntilChanged().collect {
+                            isVisible ->
+                            settingsMenu.animateVisibility(visible = isVisible)
                             if (isVisible) {
-                                View.VISIBLE
-                            } else {
-                                View.INVISIBLE
-                            }
-                    }
-                }
-
-                launch {
-                    viewModel.alpha.collect { alpha ->
-                        view.importantForAccessibility =
-                            if (alpha == 0f) {
-                                View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                            } else {
-                                View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
-                            }
-
-                        ambientIndicationArea?.alpha = alpha
-                        indicationArea.alpha = alpha
-                    }
-                }
-
-                launch {
-                    updateButtonAlpha(
-                        view = startButton,
-                        viewModel = viewModel.startButton,
-                        alphaFlow = viewModel.alpha,
-                    )
-                }
-
-                launch {
-                    updateButtonAlpha(
-                        view = endButton,
-                        viewModel = viewModel.endButton,
-                        alphaFlow = viewModel.alpha,
-                    )
-                }
-
-                launch {
-                    viewModel.indicationAreaTranslationX.collect { translationX ->
-                        indicationArea.translationX = translationX
-                        ambientIndicationArea?.translationX = translationX
-                    }
-                }
-
-                launch {
-                    combine(
-                            viewModel.isIndicationAreaPadded,
-                            configurationBasedDimensions.map { it.indicationAreaPaddingPx },
-                        ) { isPadded, paddingIfPaddedPx ->
-                            if (isPadded) {
-                                paddingIfPaddedPx
-                            } else {
-                                0
+                                vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Activated)
+                                settingsMenu.setOnTouchListener(
+                                    KeyguardSettingsButtonOnTouchListener(
+                                        view = settingsMenu,
+                                        viewModel = viewModel.settingsMenuViewModel,
+                                    )
+                                )
+                                IconViewBinder.bind(
+                                    icon = viewModel.settingsMenuViewModel.icon,
+                                    view = settingsMenu.requireViewById(R.id.icon),
+                                )
+                                TextViewBinder.bind(
+                                    view = settingsMenu.requireViewById(R.id.text),
+                                    viewModel = viewModel.settingsMenuViewModel.text,
+                                )
                             }
                         }
-                        .collect { paddingPx ->
-                            indicationArea.setPadding(paddingPx, 0, paddingPx, 0)
-                        }
-                }
+                    }
 
-                launch {
-                    configurationBasedDimensions
-                        .map { it.defaultBurnInPreventionYOffsetPx }
-                        .flatMapLatest { defaultBurnInOffsetY ->
-                            viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
-                        }
-                        .collect { translationY ->
-                            indicationArea.translationY = translationY
-                            ambientIndicationArea?.translationY = translationY
-                        }
-                }
-
-                launch {
-                    configurationBasedDimensions.collect { dimensions ->
-                        indicationText.setTextSize(
-                            TypedValue.COMPLEX_UNIT_PX,
-                            dimensions.indicationTextSizePx.toFloat(),
-                        )
-                        indicationTextBottom.setTextSize(
-                            TypedValue.COMPLEX_UNIT_PX,
-                            dimensions.indicationTextSizePx.toFloat(),
-                        )
-
-                        startButton.updateLayoutParams<ViewGroup.LayoutParams> {
-                            width = dimensions.buttonSizePx.width
-                            height = dimensions.buttonSizePx.height
-                        }
-                        endButton.updateLayoutParams<ViewGroup.LayoutParams> {
-                            width = dimensions.buttonSizePx.width
-                            height = dimensions.buttonSizePx.height
+                    // activityStarter will only be null when rendering the preview that
+                    // shows up in the Wallpaper Picker app. If we do that, then the
+                    // settings menu should never be visible.
+                    if (activityStarter != null) {
+                        launch {
+                            viewModel.settingsMenuViewModel.shouldOpenSettings
+                                .filter { it }
+                                .collect {
+                                    navigateToLockScreenSettings(
+                                        activityStarter = activityStarter,
+                                        view = settingsMenu,
+                                    )
+                                    viewModel.settingsMenuViewModel.onSettingsShown()
+                                }
                         }
                     }
                 }
             }
-        }
 
         return object : Binding {
             override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> {
@@ -253,6 +314,10 @@
 
             override fun shouldConstrainToTopOfLockIcon(): Boolean =
                 viewModel.shouldConstrainToTopOfLockIcon()
+
+            override fun destroy() {
+                disposableHandle.dispose()
+            }
         }
     }
 
@@ -265,7 +330,7 @@
         vibratorHelper: VibratorHelper?,
     ) {
         if (!viewModel.isVisible) {
-            view.isVisible = false
+            view.isInvisible = true
             return
         }
 
@@ -342,7 +407,7 @@
         if (viewModel.isClickable) {
             if (viewModel.useLongPress) {
                 view.setOnTouchListener(
-                    OnTouchListener(
+                    KeyguardQuickAffordanceOnTouchListener(
                         view,
                         viewModel,
                         messageDisplayer,
@@ -372,187 +437,21 @@
             .collect { view.alpha = it }
     }
 
-    private class OnTouchListener(
-        private val view: View,
-        private val viewModel: KeyguardQuickAffordanceViewModel,
-        private val messageDisplayer: (Int) -> Unit,
-        private val vibratorHelper: VibratorHelper?,
-        private val falsingManager: FalsingManager?,
-    ) : View.OnTouchListener {
-
-        private val longPressDurationMs = ViewConfiguration.getLongPressTimeout().toLong()
-        private var longPressAnimator: ViewPropertyAnimator? = null
-
-        @SuppressLint("ClickableViewAccessibility")
-        override fun onTouch(v: View?, event: MotionEvent?): Boolean {
-            return when (event?.actionMasked) {
-                MotionEvent.ACTION_DOWN ->
-                    if (viewModel.configKey != null) {
-                        if (isUsingAccurateTool(event)) {
-                            // For accurate tool types (stylus, mouse, etc.), we don't require a
-                            // long-press.
-                        } else {
-                            // When not using a stylus, we require a long-press to activate the
-                            // quick affordance, mostly to do "falsing" (e.g. protect from false
-                            // clicks in the pocket/bag).
-                            longPressAnimator =
-                                view
-                                    .animate()
-                                    .scaleX(PRESSED_SCALE)
-                                    .scaleY(PRESSED_SCALE)
-                                    .setDuration(longPressDurationMs)
-                                    .withEndAction {
-                                        if (
-                                            falsingManager
-                                                ?.isFalseLongTap(
-                                                    FalsingManager.MODERATE_PENALTY
-                                                ) == false
-                                        ) {
-                                            dispatchClick(viewModel.configKey)
-                                        }
-                                        cancel()
-                                    }
-                        }
-                        true
-                    } else {
-                        false
-                    }
-                MotionEvent.ACTION_MOVE -> {
-                    if (!isUsingAccurateTool(event)) {
-                        // Moving too far while performing a long-press gesture cancels that
-                        // gesture.
-                        val distanceMoved = distanceMoved(event)
-                        if (distanceMoved > ViewConfiguration.getTouchSlop()) {
-                            cancel()
-                        }
-                    }
-                    true
-                }
-                MotionEvent.ACTION_UP -> {
-                    if (isUsingAccurateTool(event)) {
-                        // When using an accurate tool type (stylus, mouse, etc.), we don't require
-                        // a long-press gesture to activate the quick affordance. Therefore, lifting
-                        // the pointer performs a click.
-                        if (
-                            viewModel.configKey != null &&
-                                distanceMoved(event) <= ViewConfiguration.getTouchSlop() &&
-                                falsingManager?.isFalseTap(FalsingManager.NO_PENALTY) == false
-                        ) {
-                            dispatchClick(viewModel.configKey)
-                        }
-                    } else {
-                        // When not using a stylus, lifting the finger/pointer will actually cancel
-                        // the long-press gesture. Calling cancel after the quick affordance was
-                        // already long-press activated is a no-op, so it's safe to call from here.
-                        cancel(
-                            onAnimationEnd =
-                                if (event.eventTime - event.downTime < longPressDurationMs) {
-                                    Runnable {
-                                        messageDisplayer.invoke(
-                                            R.string.keyguard_affordance_press_too_short
-                                        )
-                                        val amplitude =
-                                            view.context.resources
-                                                .getDimensionPixelSize(
-                                                    R.dimen.keyguard_affordance_shake_amplitude
-                                                )
-                                                .toFloat()
-                                        val shakeAnimator =
-                                            ObjectAnimator.ofFloat(
-                                                view,
-                                                "translationX",
-                                                -amplitude / 2,
-                                                amplitude / 2,
-                                            )
-                                        shakeAnimator.duration =
-                                            ShakeAnimationDuration.inWholeMilliseconds
-                                        shakeAnimator.interpolator =
-                                            CycleInterpolator(ShakeAnimationCycles)
-                                        shakeAnimator.start()
-
-                                        vibratorHelper?.vibrate(Vibrations.Shake)
-                                    }
-                                } else {
-                                    null
-                                }
-                        )
-                    }
-                    true
-                }
-                MotionEvent.ACTION_CANCEL -> {
-                    cancel()
-                    true
-                }
-                else -> false
-            }
-        }
-
-        private fun dispatchClick(
-            configKey: String,
-        ) {
-            view.setOnClickListener {
-                vibratorHelper?.vibrate(
-                    if (viewModel.isActivated) {
-                        Vibrations.Activated
-                    } else {
-                        Vibrations.Deactivated
-                    }
-                )
-                viewModel.onClicked(
-                    KeyguardQuickAffordanceViewModel.OnClickedParameters(
-                        configKey = configKey,
-                        expandable = Expandable.fromView(view),
-                        slotId = viewModel.slotId,
-                    )
-                )
-            }
-            view.performClick()
-            view.setOnClickListener(null)
-        }
-
-        private fun cancel(onAnimationEnd: Runnable? = null) {
-            longPressAnimator?.cancel()
-            longPressAnimator = null
-            view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd)
-        }
-
-        companion object {
-            private const val PRESSED_SCALE = 1.5f
-
-            /**
-             * Returns `true` if the tool type at the given pointer index is an accurate tool (like
-             * stylus or mouse), which means we can trust it to not be a false click; `false`
-             * otherwise.
-             */
-            private fun isUsingAccurateTool(
-                event: MotionEvent,
-                pointerIndex: Int = 0,
-            ): Boolean {
-                return when (event.getToolType(pointerIndex)) {
-                    MotionEvent.TOOL_TYPE_STYLUS -> true
-                    MotionEvent.TOOL_TYPE_MOUSE -> true
-                    else -> false
+    private fun View.animateVisibility(visible: Boolean) {
+        animate()
+            .withStartAction {
+                if (visible) {
+                    alpha = 0f
+                    isVisible = true
                 }
             }
-
-            /**
-             * Returns the amount of distance the pointer moved since the historical record at the
-             * [since] index.
-             */
-            private fun distanceMoved(
-                event: MotionEvent,
-                since: Int = 0,
-            ): Float {
-                return if (event.historySize > 0) {
-                    sqrt(
-                        (event.y - event.getHistoricalY(since)).pow(2) +
-                            (event.x - event.getHistoricalX(since)).pow(2)
-                    )
-                } else {
-                    0f
+            .alpha(if (visible) 1f else 0f)
+            .withEndAction {
+                if (!visible) {
+                    isVisible = false
                 }
             }
-        }
+            .start()
     }
 
     private class OnClickListener(
@@ -594,64 +493,28 @@
         )
     }
 
+    /** Opens the wallpaper picker screen after the device is unlocked by the user. */
+    private fun navigateToLockScreenSettings(
+        activityStarter: ActivityStarter,
+        view: View,
+    ) {
+        activityStarter.startActivity(
+            Intent(Intent.ACTION_SET_WALLPAPER).apply {
+                flags = Intent.FLAG_ACTIVITY_NEW_TASK
+                view.context
+                    .getString(R.string.config_wallpaperPickerPackage)
+                    .takeIf { it.isNotEmpty() }
+                    ?.let { packageName -> setPackage(packageName) }
+            },
+            /* dismissShade= */ true,
+            ActivityLaunchAnimator.Controller.fromView(view),
+        )
+    }
+
     private data class ConfigurationBasedDimensions(
         val defaultBurnInPreventionYOffsetPx: Int,
         val indicationAreaPaddingPx: Int,
         val indicationTextSizePx: Int,
         val buttonSizePx: Size,
     )
-
-    private val ShakeAnimationDuration = 300.milliseconds
-    private val ShakeAnimationCycles = 5f
-
-    object Vibrations {
-
-        private const val SmallVibrationScale = 0.3f
-        private const val BigVibrationScale = 0.6f
-
-        val Shake =
-            VibrationEffect.startComposition()
-                .apply {
-                    val vibrationDelayMs =
-                        (ShakeAnimationDuration.inWholeMilliseconds / (ShakeAnimationCycles * 2))
-                            .toInt()
-                    val vibrationCount = ShakeAnimationCycles.toInt() * 2
-                    repeat(vibrationCount) {
-                        addPrimitive(
-                            VibrationEffect.Composition.PRIMITIVE_TICK,
-                            SmallVibrationScale,
-                            vibrationDelayMs,
-                        )
-                    }
-                }
-                .compose()
-
-        val Activated =
-            VibrationEffect.startComposition()
-                .addPrimitive(
-                    VibrationEffect.Composition.PRIMITIVE_TICK,
-                    BigVibrationScale,
-                    0,
-                )
-                .addPrimitive(
-                    VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
-                    0.1f,
-                    0,
-                )
-                .compose()
-
-        val Deactivated =
-            VibrationEffect.startComposition()
-                .addPrimitive(
-                    VibrationEffect.Composition.PRIMITIVE_TICK,
-                    BigVibrationScale,
-                    0,
-                )
-                .addPrimitive(
-                    VibrationEffect.Composition.PRIMITIVE_QUICK_FALL,
-                    0.1f,
-                    0,
-                )
-                .compose()
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt
deleted file mode 100644
index d85682b..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt
+++ /dev/null
@@ -1,88 +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.keyguard.ui.binder
-
-import android.annotation.SuppressLint
-import android.view.Gravity
-import android.view.LayoutInflater
-import android.view.View
-import android.view.WindowManager
-import android.widget.PopupWindow
-import com.android.systemui.R
-import com.android.systemui.common.ui.binder.IconViewBinder
-import com.android.systemui.common.ui.binder.TextViewBinder
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsPopupMenuViewModel
-
-object KeyguardLongPressPopupViewBinder {
-    @SuppressLint("InflateParams") // We don't care that the parent is null.
-    fun createAndShow(
-        container: View,
-        viewModel: KeyguardSettingsPopupMenuViewModel,
-        onDismissed: () -> Unit,
-    ): () -> Unit {
-        val contentView: View =
-            LayoutInflater.from(container.context)
-                .inflate(
-                    R.layout.keyguard_settings_popup_menu,
-                    null,
-                )
-
-        contentView.setOnClickListener { viewModel.onClicked() }
-        IconViewBinder.bind(
-            icon = viewModel.icon,
-            view = contentView.requireViewById(R.id.icon),
-        )
-        TextViewBinder.bind(
-            view = contentView.requireViewById(R.id.text),
-            viewModel = viewModel.text,
-        )
-
-        val popupWindow =
-            PopupWindow(container.context).apply {
-                windowLayoutType = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
-                setBackgroundDrawable(null)
-                animationStyle = com.android.internal.R.style.Animation_Dialog
-                isOutsideTouchable = true
-                isFocusable = true
-                setContentView(contentView)
-                setOnDismissListener { onDismissed() }
-                contentView.measure(
-                    View.MeasureSpec.makeMeasureSpec(
-                        0,
-                        View.MeasureSpec.UNSPECIFIED,
-                    ),
-                    View.MeasureSpec.makeMeasureSpec(
-                        0,
-                        View.MeasureSpec.UNSPECIFIED,
-                    ),
-                )
-                showAtLocation(
-                    container,
-                    Gravity.NO_GRAVITY,
-                    viewModel.position.x - contentView.measuredWidth / 2,
-                    viewModel.position.y -
-                        contentView.measuredHeight -
-                        container.context.resources.getDimensionPixelSize(
-                            R.dimen.keyguard_long_press_settings_popup_vertical_offset
-                        ),
-                )
-            }
-
-        return { popupWindow.dismiss() }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
index 8671753..9cc503c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
@@ -50,10 +50,7 @@
                         return
                     }
 
-                    viewModel.onLongPress(
-                        x = x,
-                        y = y,
-                    )
+                    viewModel.onLongPress()
                 }
 
                 override fun onSingleTapDetected(view: View) {
@@ -72,23 +69,6 @@
                         view.setLongPressHandlingEnabled(isEnabled)
                     }
                 }
-
-                launch {
-                    var dismissMenu: (() -> Unit)? = null
-
-                    viewModel.menu.collect { menuOrNull ->
-                        if (menuOrNull != null) {
-                            dismissMenu =
-                                KeyguardLongPressPopupViewBinder.createAndShow(
-                                    container = view,
-                                    viewModel = menuOrNull,
-                                    onDismissed = menuOrNull.onDismissed,
-                                )
-                        } else {
-                            dismissMenu?.invoke()
-                        }
-                    }
-                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
new file mode 100644
index 0000000..5745d6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.annotation.SuppressLint
+import android.graphics.PointF
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
+import android.view.ViewPropertyAnimator
+import androidx.core.animation.CycleInterpolator
+import androidx.core.animation.ObjectAnimator
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.ui.view.rawDistanceFrom
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
+
+class KeyguardQuickAffordanceOnTouchListener(
+    private val view: View,
+    private val viewModel: KeyguardQuickAffordanceViewModel,
+    private val messageDisplayer: (Int) -> Unit,
+    private val vibratorHelper: VibratorHelper?,
+    private val falsingManager: FalsingManager?,
+) : View.OnTouchListener {
+
+    private val longPressDurationMs = ViewConfiguration.getLongPressTimeout().toLong()
+    private var longPressAnimator: ViewPropertyAnimator? = null
+    private val downDisplayCoords: PointF by lazy { PointF() }
+
+    @SuppressLint("ClickableViewAccessibility")
+    override fun onTouch(v: View, event: MotionEvent): Boolean {
+        return when (event.actionMasked) {
+            MotionEvent.ACTION_DOWN ->
+                if (viewModel.configKey != null) {
+                    downDisplayCoords.set(event.rawX, event.rawY)
+                    if (isUsingAccurateTool(event)) {
+                        // For accurate tool types (stylus, mouse, etc.), we don't require a
+                        // long-press.
+                    } else {
+                        // When not using a stylus, we require a long-press to activate the
+                        // quick affordance, mostly to do "falsing" (e.g. protect from false
+                        // clicks in the pocket/bag).
+                        longPressAnimator =
+                            view
+                                .animate()
+                                .scaleX(PRESSED_SCALE)
+                                .scaleY(PRESSED_SCALE)
+                                .setDuration(longPressDurationMs)
+                                .withEndAction {
+                                    if (
+                                        falsingManager?.isFalseLongTap(
+                                            FalsingManager.MODERATE_PENALTY
+                                        ) == false
+                                    ) {
+                                        dispatchClick(viewModel.configKey)
+                                    }
+                                    cancel()
+                                }
+                    }
+                    true
+                } else {
+                    false
+                }
+            MotionEvent.ACTION_MOVE -> {
+                if (!isUsingAccurateTool(event)) {
+                    // Moving too far while performing a long-press gesture cancels that
+                    // gesture.
+                    if (
+                        event
+                            .rawDistanceFrom(
+                                downDisplayCoords.x,
+                                downDisplayCoords.y,
+                            ) > ViewConfiguration.getTouchSlop()
+                    ) {
+                        cancel()
+                    }
+                }
+                true
+            }
+            MotionEvent.ACTION_UP -> {
+                if (isUsingAccurateTool(event)) {
+                    // When using an accurate tool type (stylus, mouse, etc.), we don't require
+                    // a long-press gesture to activate the quick affordance. Therefore, lifting
+                    // the pointer performs a click.
+                    if (
+                        viewModel.configKey != null &&
+                            event.rawDistanceFrom(downDisplayCoords.x, downDisplayCoords.y) <=
+                                ViewConfiguration.getTouchSlop() &&
+                            falsingManager?.isFalseTap(FalsingManager.NO_PENALTY) == false
+                    ) {
+                        dispatchClick(viewModel.configKey)
+                    }
+                } else {
+                    // When not using a stylus, lifting the finger/pointer will actually cancel
+                    // the long-press gesture. Calling cancel after the quick affordance was
+                    // already long-press activated is a no-op, so it's safe to call from here.
+                    cancel(
+                        onAnimationEnd =
+                            if (event.eventTime - event.downTime < longPressDurationMs) {
+                                Runnable {
+                                    messageDisplayer.invoke(
+                                        R.string.keyguard_affordance_press_too_short
+                                    )
+                                    val amplitude =
+                                        view.context.resources
+                                            .getDimensionPixelSize(
+                                                R.dimen.keyguard_affordance_shake_amplitude
+                                            )
+                                            .toFloat()
+                                    val shakeAnimator =
+                                        ObjectAnimator.ofFloat(
+                                            view,
+                                            "translationX",
+                                            -amplitude / 2,
+                                            amplitude / 2,
+                                        )
+                                    shakeAnimator.duration =
+                                        KeyguardBottomAreaVibrations.ShakeAnimationDuration
+                                            .inWholeMilliseconds
+                                    shakeAnimator.interpolator =
+                                        CycleInterpolator(
+                                            KeyguardBottomAreaVibrations.ShakeAnimationCycles
+                                        )
+                                    shakeAnimator.start()
+
+                                    vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake)
+                                }
+                            } else {
+                                null
+                            }
+                    )
+                }
+                true
+            }
+            MotionEvent.ACTION_CANCEL -> {
+                cancel()
+                true
+            }
+            else -> false
+        }
+    }
+
+    private fun dispatchClick(
+        configKey: String,
+    ) {
+        view.setOnClickListener {
+            vibratorHelper?.vibrate(
+                if (viewModel.isActivated) {
+                    KeyguardBottomAreaVibrations.Activated
+                } else {
+                    KeyguardBottomAreaVibrations.Deactivated
+                }
+            )
+            viewModel.onClicked(
+                KeyguardQuickAffordanceViewModel.OnClickedParameters(
+                    configKey = configKey,
+                    expandable = Expandable.fromView(view),
+                    slotId = viewModel.slotId,
+                )
+            )
+        }
+        view.performClick()
+        view.setOnClickListener(null)
+    }
+
+    private fun cancel(onAnimationEnd: Runnable? = null) {
+        longPressAnimator?.cancel()
+        longPressAnimator = null
+        view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd)
+    }
+
+    companion object {
+        private const val PRESSED_SCALE = 1.5f
+
+        /**
+         * Returns `true` if the tool type at the given pointer index is an accurate tool (like
+         * stylus or mouse), which means we can trust it to not be a false click; `false` otherwise.
+         */
+        private fun isUsingAccurateTool(
+            event: MotionEvent,
+            pointerIndex: Int = 0,
+        ): Boolean {
+            return when (event.getToolType(pointerIndex)) {
+                MotionEvent.TOOL_TYPE_STYLUS -> true
+                MotionEvent.TOOL_TYPE_MOUSE -> true
+                else -> false
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
new file mode 100644
index 0000000..c54203c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.graphics.PointF
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
+import com.android.systemui.animation.view.LaunchableLinearLayout
+import com.android.systemui.common.ui.view.rawDistanceFrom
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
+
+class KeyguardSettingsButtonOnTouchListener(
+    private val view: LaunchableLinearLayout,
+    private val viewModel: KeyguardSettingsMenuViewModel,
+) : View.OnTouchListener {
+
+    private val downPositionDisplayCoords = PointF()
+
+    override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
+        when (motionEvent.actionMasked) {
+            MotionEvent.ACTION_DOWN -> {
+                view.isPressed = true
+                downPositionDisplayCoords.set(motionEvent.rawX, motionEvent.rawY)
+                viewModel.onTouchGestureStarted()
+            }
+            MotionEvent.ACTION_UP -> {
+                view.isPressed = false
+                val distanceMoved =
+                    motionEvent
+                        .rawDistanceFrom(downPositionDisplayCoords.x, downPositionDisplayCoords.y)
+                val isClick = distanceMoved < ViewConfiguration.getTouchSlop()
+                viewModel.onTouchGestureEnded(isClick)
+                if (isClick) {
+                    view.performClick()
+                }
+            }
+            MotionEvent.ACTION_CANCEL -> {
+                view.isPressed = false
+                viewModel.onTouchGestureEnded(/* isClick= */ false)
+            }
+        }
+
+        return true
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index a8e3464..2d83be95 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -44,6 +44,8 @@
     private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
     private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
     private val burnInHelperWrapper: BurnInHelperWrapper,
+    private val longPressViewModel: KeyguardLongPressViewModel,
+    val settingsMenuViewModel: KeyguardSettingsMenuViewModel,
 ) {
     data class PreviewMode(
         val isInPreviewMode: Boolean = false,
@@ -161,6 +163,14 @@
         selectedPreviewSlotId.value = slotId
     }
 
+    /**
+     * Notifies that some input gesture has started somewhere in the bottom area that's outside of
+     * the lock screen settings menu item pop-up.
+     */
+    fun onTouchedOutsideLockScreenSettingsMenu() {
+        longPressViewModel.onTouchedOutside()
+    }
+
     private fun button(
         position: KeyguardQuickAffordancePosition
     ): Flow<KeyguardQuickAffordanceViewModel> {
@@ -225,9 +235,10 @@
                     isDimmed = isDimmed,
                     slotId = slotId,
                 )
-            is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel(
-                slotId = slotId,
-            )
+            is KeyguardQuickAffordanceModel.Hidden ->
+                KeyguardQuickAffordanceViewModel(
+                    slotId = slotId,
+                )
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt
index d896390..c73931a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt
@@ -17,15 +17,13 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.R
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
 
 /** Models UI state to support the lock screen long-press feature. */
+@SysUISingleton
 class KeyguardLongPressViewModel
 @Inject
 constructor(
@@ -35,35 +33,16 @@
     /** Whether the long-press handling feature should be enabled. */
     val isLongPressHandlingEnabled: Flow<Boolean> = interactor.isLongPressHandlingEnabled
 
-    /** View-model for a menu that should be shown; `null` when no menu should be shown. */
-    val menu: Flow<KeyguardSettingsPopupMenuViewModel?> =
-        interactor.menu.map { model ->
-            model?.let {
-                KeyguardSettingsPopupMenuViewModel(
-                    icon =
-                        Icon.Resource(
-                            res = R.drawable.ic_settings,
-                            contentDescription = null,
-                        ),
-                    text =
-                        Text.Resource(
-                            res = R.string.lock_screen_settings,
-                        ),
-                    position = model.position,
-                    onClicked = model.onClicked,
-                    onDismissed = model.onDismissed,
-                )
-            }
-        }
-
     /** Notifies that the user has long-pressed on the lock screen. */
-    fun onLongPress(
-        x: Int,
-        y: Int,
-    ) {
-        interactor.onLongPress(
-            x = x,
-            y = y,
-        )
+    fun onLongPress() {
+        interactor.onLongPress()
+    }
+
+    /**
+     * Notifies that some input gesture has started somewhere outside of the lock screen settings
+     * menu item pop-up.
+     */
+    fun onTouchedOutside() {
+        interactor.onTouchedOutside()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsMenuViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsMenuViewModel.kt
new file mode 100644
index 0000000..c36da9d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsMenuViewModel.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.R
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Models the UI state of a keyguard settings popup menu. */
+class KeyguardSettingsMenuViewModel
+@Inject
+constructor(
+    private val interactor: KeyguardLongPressInteractor,
+) {
+    val isVisible: Flow<Boolean> = interactor.isMenuVisible
+    val shouldOpenSettings: Flow<Boolean> = interactor.shouldOpenSettings
+
+    val icon: Icon =
+        Icon.Resource(
+            res = R.drawable.ic_palette,
+            contentDescription = null,
+        )
+
+    val text: Text =
+        Text.Resource(
+            res = R.string.lock_screen_settings,
+        )
+
+    fun onTouchGestureStarted() {
+        interactor.onMenuTouchGestureStarted()
+    }
+
+    fun onTouchGestureEnded(isClick: Boolean) {
+        interactor.onMenuTouchGestureEnded(
+            isClick = isClick,
+        )
+    }
+
+    fun onSettingsShown() {
+        interactor.onSettingsShown()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.kt
deleted file mode 100644
index 0571b05..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.kt
+++ /dev/null
@@ -1,34 +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.keyguard.ui.viewmodel
-
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.shared.model.Position
-import com.android.systemui.common.shared.model.Text
-
-/** Models the UI state of a keyguard settings popup menu. */
-data class KeyguardSettingsPopupMenuViewModel(
-    val icon: Icon,
-    val text: Text,
-    /** Where the menu should be anchored, roughly in screen space. */
-    val position: Position,
-    /** Callback to invoke when the menu gets clicked by the user. */
-    val onClicked: () -> Unit,
-    /** Callback to invoke when the menu gets dismissed by the user. */
-    val onDismissed: () -> Unit,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 9d2d355..faaa205 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -18,7 +18,7 @@
 
 import android.os.Trace
 import com.android.systemui.Dumpable
-import com.android.systemui.plugins.util.RingBuffer
+import com.android.systemui.common.buffer.RingBuffer
 import com.android.systemui.util.time.SystemClock
 import java.io.PrintWriter
 import java.text.SimpleDateFormat
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index c4e76b2..ccddd1d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -60,7 +60,7 @@
 import dagger.Lazy;
 
 public class MediaProjectionPermissionActivity extends Activity
-        implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+        implements DialogInterface.OnClickListener {
     private static final String TAG = "MediaProjectionPermissionActivity";
     private static final float MAX_APP_NAME_SIZE_PX = 500f;
     private static final String ELLIPSIS = "\u2026";
@@ -215,7 +215,8 @@
         SystemUIDialog.applyFlags(dialog);
         SystemUIDialog.setDialogSize(dialog);
 
-        dialog.setOnCancelListener(this);
+        dialog.setOnCancelListener(this::onDialogDismissedOrCancelled);
+        dialog.setOnDismissListener(this::onDialogDismissedOrCancelled);
         dialog.create();
         dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
 
@@ -283,9 +284,10 @@
         return intent;
     }
 
-    @Override
-    public void onCancel(DialogInterface dialog) {
-        finish();
+    private void onDialogDismissedOrCancelled(DialogInterface dialogInterface) {
+        if (!isFinishing()) {
+            finish();
+        }
     }
 
     private boolean isPartialScreenSharingEnabled() {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
index 70040c7..c804df8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
@@ -363,12 +363,8 @@
     }
 
     fun popOffEdge(startingVelocity: Float) {
-        val heightStretchAmount = startingVelocity * 50
-        val widthStretchAmount = startingVelocity * 150
-        val scaleStretchAmount = startingVelocity * 0.8f
-        backgroundHeight.stretchTo(stretchAmount = 0f, startingVelocity = -heightStretchAmount)
-        backgroundWidth.stretchTo(stretchAmount = 0f, startingVelocity = widthStretchAmount)
-        scale.stretchTo(stretchAmount = 0f, startingVelocity = -scaleStretchAmount)
+        scale.stretchTo(stretchAmount = 0f, startingVelocity = startingVelocity * -.8f)
+        horizontalTranslation.stretchTo(stretchAmount = 0f, startingVelocity * 200f)
     }
 
     fun popScale(startingVelocity: Float) {
@@ -410,7 +406,7 @@
         arrowAlpha.updateRestingPosition(restingParams.arrowDimens.alpha, animate)
         arrowLength.updateRestingPosition(restingParams.arrowDimens.length, animate)
         arrowHeight.updateRestingPosition(restingParams.arrowDimens.height, animate)
-        scalePivotX.updateRestingPosition(restingParams.backgroundDimens.width, animate)
+        scalePivotX.updateRestingPosition(restingParams.scalePivotX, animate)
         backgroundWidth.updateRestingPosition(restingParams.backgroundDimens.width, animate)
         backgroundHeight.updateRestingPosition(restingParams.backgroundDimens.height, animate)
         backgroundEdgeCornerRadius.updateRestingPosition(
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index a29eb3b..3770b28 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -49,6 +49,7 @@
 
 private const val TAG = "BackPanelController"
 private const val ENABLE_FAILSAFE = true
+private const val FAILSAFE_DELAY_MS = 350L
 
 private const val PX_PER_SEC = 1000
 private const val PX_PER_MS = 1
@@ -64,9 +65,9 @@
 private const val MIN_DURATION_ENTRY_TO_ACTIVE_CONSIDERED_AS_FLING = 100L
 private const val MIN_DURATION_INACTIVE_TO_ACTIVE_CONSIDERED_AS_FLING = 400L
 
-private const val FAILSAFE_DELAY_MS = 350L
-private const val POP_ON_FLING_DELAY = 50L
-private const val POP_ON_FLING_SCALE = 3f
+private const val POP_ON_FLING_DELAY = 60L
+private const val POP_ON_FLING_SCALE = 2f
+private const val POP_ON_COMMITTED_SCALE = 3f
 
 internal val VIBRATE_ACTIVATED_EFFECT =
         VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
@@ -774,7 +775,9 @@
                             GestureState.ENTRY,
                             GestureState.INACTIVE,
                             GestureState.CANCELLED -> params.preThresholdIndicator.scalePivotX
-                            else -> params.committedIndicator.scalePivotX
+                            GestureState.ACTIVE -> params.activeIndicator.scalePivotX
+                            GestureState.FLUNG,
+                            GestureState.COMMITTED -> params.committedIndicator.scalePivotX
                         },
                         horizontalTranslation = when (currentState) {
                             GestureState.GONE -> {
@@ -921,7 +924,7 @@
                     mainHandler.postDelayed(onEndSetGoneStateListener.runnable,
                             MIN_DURATION_COMMITTED_AFTER_FLING_ANIMATION)
                 } else {
-                    mView.popScale(POP_ON_FLING_SCALE)
+                    mView.popScale(POP_ON_COMMITTED_SCALE)
                     mainHandler.postDelayed(onAlphaEndSetGoneStateListener.runnable,
                             MIN_DURATION_COMMITTED_ANIMATION)
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
index 6ce6f0d..c9d8c84 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -35,7 +35,7 @@
     data class BackIndicatorDimens(
             val horizontalTranslation: Float? = 0f,
             val scale: Float = 0f,
-            val scalePivotX: Float = 0f,
+            val scalePivotX: Float? = null,
             val arrowDimens: ArrowDimens,
             val backgroundDimens: BackgroundDimens,
             val verticalTranslationSpring: SpringForce? = null,
@@ -203,7 +203,8 @@
                 horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin),
                 scale = getDimenFloat(R.dimen.navigation_edge_active_scale),
                 horizontalTranslationSpring = entryActiveHorizontalTranslationSpring,
-                scaleSpring = createSpring(450f, 0.415f),
+                scaleSpring = createSpring(450f, 0.39f),
+                scalePivotX = getDimen(R.dimen.navigation_edge_active_background_width),
                 arrowDimens = ArrowDimens(
                         length = getDimen(R.dimen.navigation_edge_active_arrow_length),
                         height = getDimen(R.dimen.navigation_edge_active_arrow_height),
@@ -258,6 +259,7 @@
 
         committedIndicator = activeIndicator.copy(
                 horizontalTranslation = null,
+                scalePivotX = null,
                 arrowDimens = activeIndicator.arrowDimens.copy(
                         lengthSpring = activeCommittedArrowLengthSpring,
                         heightSpring = activeCommittedArrowHeightSpring,
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
index 44855fb..0f38d32 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
@@ -18,9 +18,11 @@
 
 import android.content.Context
 import android.content.Intent
-import android.content.pm.UserInfo
+import android.os.Build
 import android.os.Bundle
+import android.os.UserHandle
 import android.os.UserManager
+import android.util.Log
 import androidx.activity.ComponentActivity
 import com.android.systemui.notetask.NoteTaskController
 import com.android.systemui.notetask.NoteTaskEntryPoint
@@ -63,9 +65,13 @@
         //        | Bubble#showOrHideAppBubble |   <--------------
         //        |      (with WP user ID)     |
         //         ----------------------------
-        val mainUser: UserInfo? = userTracker.userProfiles.firstOrNull { it.isMain }
-        if (userManager.isManagedProfile && mainUser != null) {
-            controller.startNoteTaskProxyActivityForUser(mainUser.userHandle)
+        val mainUser: UserHandle? = userManager.mainUser
+        if (userManager.isManagedProfile) {
+            if (mainUser == null) {
+                logDebug { "Can't find the main user. Skipping the notes app launch." }
+            } else {
+                controller.startNoteTaskProxyActivityForUser(mainUser)
+            }
         } else {
             controller.showNoteTask(entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT)
         }
@@ -83,3 +89,8 @@
         }
     }
 }
+
+/** [Log.println] a [Log.DEBUG] message, only when [Build.IS_DEBUGGABLE]. */
+private inline fun Any.logDebug(message: () -> String) {
+    if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName.orEmpty(), message())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index c2c1306..a765702 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -18,6 +18,10 @@
 
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_CONFIRMATION;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_LOW_WARNING;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SaverManualEnabledReason;
+
 import android.app.Dialog;
 import android.app.KeyguardManager;
 import android.app.Notification;
@@ -691,7 +695,7 @@
             d.setTitle(R.string.battery_saver_confirmation_title);
             d.setPositiveButton(R.string.battery_saver_confirmation_ok,
                     (dialog, which) -> {
-                        setSaverMode(true, false);
+                        setSaverMode(true, false, SAVER_ENABLED_CONFIRMATION);
                         logEvent(BatteryWarningEvents.LowBatteryWarningEvent.SAVER_CONFIRM_OK);
                     });
             d.setNegativeButton(android.R.string.cancel, (dialog, which) ->
@@ -790,8 +794,9 @@
         return builder;
     }
 
-    private void setSaverMode(boolean mode, boolean needFirstTimeWarning) {
-        BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning);
+    private void setSaverMode(boolean mode, boolean needFirstTimeWarning,
+            @SaverManualEnabledReason int reason) {
+        BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning, reason);
     }
 
     private void startBatterySaverSchedulePage() {
@@ -839,7 +844,7 @@
             } else if (action.equals(ACTION_START_SAVER)) {
                 logEvent(BatteryWarningEvents
                         .LowBatteryWarningEvent.LOW_BATTERY_NOTIFICATION_TURN_ON);
-                setSaverMode(true, true);
+                setSaverMode(true, true, SAVER_ENABLED_LOW_WARNING);
                 dismissLowBatteryNotification();
             } else if (action.equals(ACTION_SHOW_START_SAVER_CONFIRMATION)) {
                 dismissLowBatteryNotification();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index ce690e2..57b479e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -26,8 +26,6 @@
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTileView;
-import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
-import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.util.leak.GarbageMonitor;
 
 import java.util.ArrayList;
@@ -35,7 +33,7 @@
 import java.util.Collection;
 import java.util.List;
 
-public interface QSHost extends PanelInteractor, CustomTileAddedRepository {
+public interface QSHost {
     String TILES_SETTING = Settings.Secure.QS_TILES;
     int POSITION_AT_END = -1;
 
@@ -75,7 +73,11 @@
      * @see QSFactory#createTileView
      */
     QSTileView createTileView(Context themedContext, QSTile tile, boolean collapsedView);
-    /** Create a {@link QSTile} of a {@code tileSpec} type. */
+    /** Create a {@link QSTile} of a {@code tileSpec} type.
+     *
+     * This should only be called by classes that need to create one-off instances of tiles.
+     * Do not use to create {@code custom} tiles without explicitly taking care of its lifecycle.
+     */
     QSTile createTile(String tileSpec);
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
new file mode 100644
index 0000000..14acb4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.content.ComponentName
+import android.content.Context
+import androidx.annotation.GuardedBy
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.qs.QSTileView
+import com.android.systemui.qs.external.TileServiceRequestController
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+/**
+ * Adapter to determine what real class to use for classes that depend on [QSHost].
+ *
+ * * When [Flags.QS_PIPELINE_NEW_HOST] is off, all calls will be routed to [QSTileHost].
+ * * When [Flags.QS_PIPELINE_NEW_HOST] is on, calls regarding the current set of tiles will be
+ *   routed to [CurrentTilesInteractor]. Other calls (like [warn]) will still be routed to
+ *   [QSTileHost].
+ *
+ * This routing also includes dumps.
+ */
+@SysUISingleton
+class QSHostAdapter
+@Inject
+constructor(
+    private val qsTileHost: QSTileHost,
+    private val interactor: CurrentTilesInteractor,
+    private val context: Context,
+    private val tileServiceRequestControllerBuilder: TileServiceRequestController.Builder,
+    @Application private val scope: CoroutineScope,
+    private val featureFlags: FeatureFlags,
+    dumpManager: DumpManager,
+) : QSHost {
+
+    companion object {
+        private const val TAG = "QSTileHost"
+    }
+
+    private val useNewHost = featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)
+
+    @GuardedBy("callbacksMap") private val callbacksMap = mutableMapOf<QSHost.Callback, Job>()
+
+    init {
+        scope.launch { tileServiceRequestControllerBuilder.create(this@QSHostAdapter).init() }
+        // Redirect dump to the correct host (needed for CTS tests)
+        dumpManager.registerCriticalDumpable(
+            TAG,
+            if (useNewHost) interactor else qsTileHost
+        )
+    }
+
+    override fun getTiles(): Collection<QSTile> {
+        return if (useNewHost) {
+            interactor.currentQSTiles
+        } else {
+            qsTileHost.getTiles()
+        }
+    }
+
+    override fun getSpecs(): List<String> {
+        return if (useNewHost) {
+            interactor.currentTilesSpecs.map { it.spec }
+        } else {
+            qsTileHost.getSpecs()
+        }
+    }
+
+    override fun removeTile(spec: String) {
+        if (useNewHost) {
+            interactor.removeTiles(listOf(TileSpec.create(spec)))
+        } else {
+            qsTileHost.removeTile(spec)
+        }
+    }
+
+    override fun addCallback(callback: QSHost.Callback) {
+        if (useNewHost) {
+            val job =
+                scope.launch {
+                    interactor.currentTiles.collect { callback.onTilesChanged() }
+                }
+            synchronized(callbacksMap) { callbacksMap.put(callback, job) }
+        } else {
+            qsTileHost.addCallback(callback)
+        }
+    }
+
+    override fun removeCallback(callback: QSHost.Callback) {
+        if (useNewHost) {
+            synchronized(callbacksMap) { callbacksMap.get(callback)?.cancel() }
+        } else {
+            qsTileHost.removeCallback(callback)
+        }
+    }
+
+    override fun removeTiles(specs: Collection<String>) {
+        if (useNewHost) {
+            interactor.removeTiles(specs.map(TileSpec::create))
+        } else {
+            qsTileHost.removeTiles(specs)
+        }
+    }
+
+    override fun removeTileByUser(component: ComponentName) {
+        if (useNewHost) {
+            interactor.removeTiles(listOf(TileSpec.create(component)))
+        } else {
+            qsTileHost.removeTileByUser(component)
+        }
+    }
+
+    override fun addTile(spec: String, position: Int) {
+        if (useNewHost) {
+            interactor.addTile(TileSpec.create(spec), position)
+        } else {
+            qsTileHost.addTile(spec, position)
+        }
+    }
+
+    override fun addTile(component: ComponentName, end: Boolean) {
+        if (useNewHost) {
+            interactor.addTile(
+                TileSpec.create(component),
+                if (end) POSITION_AT_END else 0
+            )
+        } else {
+            qsTileHost.addTile(component, end)
+        }
+    }
+
+    override fun changeTilesByUser(previousTiles: List<String>, newTiles: List<String>) {
+        if (useNewHost) {
+            interactor.setTiles(newTiles.map(TileSpec::create))
+        } else {
+            qsTileHost.changeTilesByUser(previousTiles, newTiles)
+        }
+    }
+
+    override fun warn(message: String?, t: Throwable?) {
+        qsTileHost.warn(message, t)
+    }
+
+    override fun getContext(): Context {
+        return if (useNewHost) {
+            context
+        } else {
+            qsTileHost.context
+        }
+    }
+
+    override fun getUserContext(): Context {
+        return if (useNewHost) {
+            interactor.userContext.value
+        } else {
+            qsTileHost.userContext
+        }
+    }
+
+    override fun getUserId(): Int {
+        return if (useNewHost) {
+            interactor.userId.value
+        } else {
+            qsTileHost.userId
+        }
+    }
+
+    override fun getUiEventLogger(): UiEventLogger {
+        return qsTileHost.uiEventLogger
+    }
+
+    override fun createTileView(
+        themedContext: Context?,
+        tile: QSTile?,
+        collapsedView: Boolean
+    ): QSTileView {
+        return qsTileHost.createTileView(themedContext, tile, collapsedView)
+    }
+
+    override fun createTile(tileSpec: String): QSTile? {
+        return qsTileHost.createTile(tileSpec)
+    }
+
+    override fun addTile(spec: String) {
+        return addTile(spec, QSHost.POSITION_AT_END)
+    }
+
+    override fun addTile(tile: ComponentName) {
+        return addTile(tile, false)
+    }
+
+    override fun indexOf(tileSpec: String): Int {
+        return specs.indexOf(tileSpec)
+    }
+
+    override fun getNewInstanceId(): InstanceId {
+        return qsTileHost.newInstanceId
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 8bbdeed..0ca8973 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -37,8 +37,9 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.nano.SystemUIProtoDump;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.qs.QSFactory;
@@ -48,9 +49,10 @@
 import com.android.systemui.qs.external.CustomTileStatePersister;
 import com.android.systemui.qs.external.TileLifecycleManager;
 import com.android.systemui.qs.external.TileServiceKey;
-import com.android.systemui.qs.external.TileServiceRequestController;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.nano.QsTileState;
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.AutoTileManager;
@@ -85,7 +87,8 @@
  * This class also provides the interface for adding/removing/changing tiles.
  */
 @SysUISingleton
-public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable {
+public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable,
+        PanelInteractor, CustomTileAddedRepository {
     private static final String TAG = "QSTileHost";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final int MAX_QS_INSTANCE_ID = 1 << 20;
@@ -99,7 +102,6 @@
     private final ArrayList<String> mTileSpecs = new ArrayList<>();
     private final TunerService mTunerService;
     private final PluginManager mPluginManager;
-    private final DumpManager mDumpManager;
     private final QSLogger mQSLogger;
     private final UiEventLogger mUiEventLogger;
     private final InstanceIdSequence mInstanceIdSequence;
@@ -122,9 +124,10 @@
     // This is enforced by only cleaning the flag at the end of a successful run of #onTuningChanged
     private boolean mTilesListDirty = true;
 
-    private final TileServiceRequestController mTileServiceRequestController;
     private TileLifecycleManager.Factory mTileLifeCycleManagerFactory;
 
+    private final FeatureFlags mFeatureFlags;
+
     @Inject
     public QSTileHost(Context context,
             QSFactory defaultFactory,
@@ -132,35 +135,32 @@
             PluginManager pluginManager,
             TunerService tunerService,
             Provider<AutoTileManager> autoTiles,
-            DumpManager dumpManager,
             Optional<CentralSurfaces> centralSurfacesOptional,
             QSLogger qsLogger,
             UiEventLogger uiEventLogger,
             UserTracker userTracker,
             SecureSettings secureSettings,
             CustomTileStatePersister customTileStatePersister,
-            TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
             TileLifecycleManager.Factory tileLifecycleManagerFactory,
-            UserFileManager userFileManager
+            UserFileManager userFileManager,
+            FeatureFlags featureFlags
     ) {
         mContext = context;
         mUserContext = context;
         mTunerService = tunerService;
         mPluginManager = pluginManager;
-        mDumpManager = dumpManager;
         mQSLogger = qsLogger;
         mUiEventLogger = uiEventLogger;
         mMainExecutor = mainExecutor;
-        mTileServiceRequestController = tileServiceRequestControllerBuilder.create(this);
         mTileLifeCycleManagerFactory = tileLifecycleManagerFactory;
         mUserFileManager = userFileManager;
+        mFeatureFlags = featureFlags;
 
         mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID);
         mCentralSurfacesOptional = centralSurfacesOptional;
 
         mQsFactories.add(defaultFactory);
         pluginManager.addPluginListener(this, QSFactory.class, true);
-        mDumpManager.registerDumpable(TAG, this);
         mUserTracker = userTracker;
         mSecureSettings = secureSettings;
         mCustomTileStatePersister = customTileStatePersister;
@@ -172,7 +172,6 @@
             tunerService.addTunable(this, TILES_SETTING);
             // AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
             mAutoTiles = autoTiles.get();
-            mTileServiceRequestController.init();
         });
     }
 
@@ -186,8 +185,6 @@
         mAutoTiles.destroy();
         mTunerService.removeTunable(this);
         mPluginManager.removePluginListener(this);
-        mDumpManager.unregisterDumpable(TAG);
-        mTileServiceRequestController.destroy();
     }
 
     @Override
@@ -300,6 +297,10 @@
         if (!TILES_SETTING.equals(key)) {
             return;
         }
+        // Do not process tiles if the flag is enabled.
+        if (mFeatureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
+            return;
+        }
         if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
             newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
index 964fe71..3ddd9f1c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QSHostAdapter
 import com.android.systemui.qs.QSTileHost
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedSharedPrefsRepository
@@ -31,7 +32,7 @@
 @Module
 interface QSHostModule {
 
-    @Binds fun provideQsHost(controllerImpl: QSTileHost): QSHost
+    @Binds fun provideQsHost(controllerImpl: QSHostAdapter): QSHost
 
     @Module
     companion object {
@@ -39,7 +40,7 @@
         @JvmStatic
         fun providePanelInteractor(
             featureFlags: FeatureFlags,
-            qsHost: QSHost,
+            qsHost: QSTileHost,
             panelInteractorImpl: PanelInteractorImpl
         ): PanelInteractor {
             return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
@@ -53,7 +54,7 @@
         @JvmStatic
         fun provideCustomTileAddedRepository(
             featureFlags: FeatureFlags,
-            qsHost: QSHost,
+            qsHost: QSTileHost,
             customTileAddedRepository: CustomTileAddedSharedPrefsRepository
         ): CustomTileAddedRepository {
             return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index 00f0a67..e212bc4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
 import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractorImpl
 import com.android.systemui.qs.pipeline.prototyping.PrototypeCoreStartable
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import dagger.Binds
@@ -38,6 +40,11 @@
     abstract fun provideTileSpecRepository(impl: TileSpecSettingsRepository): TileSpecRepository
 
     @Binds
+    abstract fun bindCurrentTilesInteractor(
+        impl: CurrentTilesInteractorImpl
+    ): CurrentTilesInteractor
+
+    @Binds
     @IntoMap
     @ClassKey(PrototypeCoreStartable::class)
     abstract fun providePrototypeCoreStartable(startable: PrototypeCoreStartable): CoreStartable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index d254e1b..595b29a9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -32,6 +32,7 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -53,6 +54,8 @@
      * at the end of the list.
      *
      * Passing [TileSpec.Invalid] is a noop.
+     *
+     * Trying to add a tile beyond the end of the list will add it at the end.
      */
     suspend fun addTile(@UserIdInt userId: Int, tile: TileSpec, position: Int = POSITION_AT_END)
 
@@ -61,7 +64,7 @@
      *
      * Passing [TileSpec.Invalid] or a non present tile is a noop.
      */
-    suspend fun removeTile(@UserIdInt userId: Int, tile: TileSpec)
+    suspend fun removeTiles(@UserIdInt userId: Int, tiles: Collection<TileSpec>)
 
     /**
      * Sets the list of current [tiles] for a given [userId].
@@ -106,6 +109,7 @@
             }
             .onStart { emit(Unit) }
             .map { secureSettings.getStringForUser(SETTING, userId) ?: "" }
+            .distinctUntilChanged()
             .onEach { logger.logTilesChangedInSettings(it, userId) }
             .map { parseTileSpecs(it, userId) }
             .flowOn(backgroundDispatcher)
@@ -117,7 +121,7 @@
         }
         val tilesList = loadTiles(userId).toMutableList()
         if (tile !in tilesList) {
-            if (position < 0) {
+            if (position < 0 || position >= tilesList.size) {
                 tilesList.add(tile)
             } else {
                 tilesList.add(position, tile)
@@ -126,12 +130,12 @@
         }
     }
 
-    override suspend fun removeTile(userId: Int, tile: TileSpec) {
-        if (tile == TileSpec.Invalid) {
+    override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) {
+        if (tiles.all { it == TileSpec.Invalid }) {
             return
         }
         val tilesList = loadTiles(userId).toMutableList()
-        if (tilesList.remove(tile)) {
+        if (tilesList.removeAll(tiles)) {
             storeTiles(userId, tilesList.toList())
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
new file mode 100644
index 0000000..91c6e8b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.UserHandle
+import com.android.systemui.Dumpable
+import com.android.systemui.ProtoDumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.nano.SystemUIProtoDump
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.qs.QSFactory
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.external.CustomTile
+import com.android.systemui.qs.external.CustomTileStatePersister
+import com.android.systemui.qs.external.TileLifecycleManager
+import com.android.systemui.qs.external.TileServiceKey
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import com.android.systemui.qs.pipeline.domain.model.TileModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.qs.toProto
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.pairwise
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Interactor for retrieving the list of current QS tiles, as well as making changes to this list
+ *
+ * It is [ProtoDumpable] as it needs to be able to dump state for CTS tests.
+ */
+interface CurrentTilesInteractor : ProtoDumpable {
+    /** Current list of tiles with their corresponding spec. */
+    val currentTiles: StateFlow<List<TileModel>>
+
+    /** User for the [currentTiles]. */
+    val userId: StateFlow<Int>
+
+    /** [Context] corresponding to [userId] */
+    val userContext: StateFlow<Context>
+
+    /** List of specs corresponding to the last value of [currentTiles] */
+    val currentTilesSpecs: List<TileSpec>
+        get() = currentTiles.value.map(TileModel::spec)
+
+    /** List of tiles corresponding to the last value of [currentTiles] */
+    val currentQSTiles: List<QSTile>
+        get() = currentTiles.value.map(TileModel::tile)
+
+    /**
+     * Requests that a tile be added in the list of tiles for the current user.
+     *
+     * @see TileSpecRepository.addTile
+     */
+    fun addTile(spec: TileSpec, position: Int = TileSpecRepository.POSITION_AT_END)
+
+    /**
+     * Requests that tiles be removed from the list of tiles for the current user
+     *
+     * If tiles with [TileSpec.CustomTileSpec] are removed, their lifecycle will be terminated and
+     * marked as removed.
+     *
+     * @see TileSpecRepository.removeTiles
+     */
+    fun removeTiles(specs: Collection<TileSpec>)
+
+    /**
+     * Requests that the list of tiles for the current user is changed to [specs].
+     *
+     * If tiles with [TileSpec.CustomTileSpec] are removed, their lifecycle will be terminated and
+     * marked as removed.
+     *
+     * @see TileSpecRepository.setTiles
+     */
+    fun setTiles(specs: List<TileSpec>)
+}
+
+/**
+ * This implementation of [CurrentTilesInteractor] will try to re-use existing [QSTile] objects when
+ * possible, in particular:
+ * * It will only destroy tiles when they are not part of the list of tiles anymore
+ * * Platform tiles will be kept between users, with a call to [QSTile.userSwitch]
+ * * [CustomTile]s will only be destroyed if the user changes.
+ */
+@SysUISingleton
+class CurrentTilesInteractorImpl
+@Inject
+constructor(
+    private val tileSpecRepository: TileSpecRepository,
+    private val userRepository: UserRepository,
+    private val customTileStatePersister: CustomTileStatePersister,
+    private val tileFactory: QSFactory,
+    private val customTileAddedRepository: CustomTileAddedRepository,
+    private val tileLifecycleManagerFactory: TileLifecycleManager.Factory,
+    private val userTracker: UserTracker,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    @Application private val scope: CoroutineScope,
+    private val logger: QSPipelineLogger,
+    featureFlags: FeatureFlags,
+) : CurrentTilesInteractor {
+
+    private val _currentSpecsAndTiles: MutableStateFlow<List<TileModel>> =
+        MutableStateFlow(emptyList())
+
+    override val currentTiles: StateFlow<List<TileModel>> = _currentSpecsAndTiles.asStateFlow()
+
+    // This variable should only be accessed inside the collect of `startTileCollection`.
+    private val specsToTiles = mutableMapOf<TileSpec, QSTile>()
+
+    private val currentUser = MutableStateFlow(userTracker.userId)
+    override val userId = currentUser.asStateFlow()
+
+    private val _userContext = MutableStateFlow(userTracker.userContext)
+    override val userContext = _userContext.asStateFlow()
+
+    init {
+        if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
+            startTileCollection()
+        }
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    private fun startTileCollection() {
+        scope.launch {
+            userRepository.selectedUserInfo
+                .flatMapLatest { user ->
+                    currentUser.value = user.id
+                    _userContext.value = userTracker.userContext
+                    tileSpecRepository.tilesSpecs(user.id).map { user.id to it }
+                }
+                .distinctUntilChanged()
+                .pairwise(-1 to emptyList())
+                .flowOn(backgroundDispatcher)
+                .collect { (old, new) ->
+                    val newTileList = new.second
+                    val userChanged = old.first != new.first
+                    val newUser = new.first
+
+                    // Destroy all tiles that are not in the new set
+                    specsToTiles
+                        .filter { it.key !in newTileList }
+                        .forEach { entry ->
+                            logger.logTileDestroyed(
+                                entry.key,
+                                if (userChanged) {
+                                    QSPipelineLogger.TileDestroyedReason
+                                        .TILE_NOT_PRESENT_IN_NEW_USER
+                                } else {
+                                    QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
+                                }
+                            )
+                            entry.value.destroy()
+                        }
+                    // MutableMap will keep the insertion order
+                    val newTileMap = mutableMapOf<TileSpec, QSTile>()
+
+                    newTileList.forEach { tileSpec ->
+                        if (tileSpec !in newTileMap) {
+                            val newTile =
+                                if (tileSpec in specsToTiles) {
+                                    processExistingTile(
+                                        tileSpec,
+                                        specsToTiles.getValue(tileSpec),
+                                        userChanged,
+                                        newUser
+                                    )
+                                        ?: createTile(tileSpec)
+                                } else {
+                                    createTile(tileSpec)
+                                }
+                            if (newTile != null) {
+                                newTileMap[tileSpec] = newTile
+                            }
+                        }
+                    }
+
+                    val resolvedSpecs = newTileMap.keys.toList()
+                    specsToTiles.clear()
+                    specsToTiles.putAll(newTileMap)
+                    _currentSpecsAndTiles.value = newTileMap.map { TileModel(it.key, it.value) }
+                    if (resolvedSpecs != newTileList) {
+                        // There were some tiles that couldn't be created. Change the value in the
+                        // repository
+                        launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) }
+                    }
+                }
+        }
+    }
+
+    override fun addTile(spec: TileSpec, position: Int) {
+        scope.launch {
+            tileSpecRepository.addTile(userRepository.getSelectedUserInfo().id, spec, position)
+        }
+    }
+
+    override fun removeTiles(specs: Collection<TileSpec>) {
+        val currentSpecsCopy = currentTilesSpecs.toSet()
+        val user = currentUser.value
+        // intersect: tiles that are there and are being removed
+        val toFree = currentSpecsCopy.intersect(specs).filterIsInstance<TileSpec.CustomTileSpec>()
+        toFree.forEach { onCustomTileRemoved(it.componentName, user) }
+        if (currentSpecsCopy.intersect(specs).isNotEmpty()) {
+            // We don't want to do the call to set in case getCurrentTileSpecs is not the most
+            // up to date for this user.
+            scope.launch { tileSpecRepository.removeTiles(user, specs) }
+        }
+    }
+
+    override fun setTiles(specs: List<TileSpec>) {
+        val currentSpecsCopy = currentTilesSpecs
+        val user = currentUser.value
+        if (currentSpecsCopy != specs) {
+            // minus: tiles that were there but are not there anymore
+            val toFree = currentSpecsCopy.minus(specs).filterIsInstance<TileSpec.CustomTileSpec>()
+            toFree.forEach { onCustomTileRemoved(it.componentName, user) }
+            scope.launch { tileSpecRepository.setTiles(user, specs) }
+        }
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("CurrentTileInteractorImpl:")
+        pw.println("User: ${userId.value}")
+        currentTiles.value
+            .map { it.tile }
+            .filterIsInstance<Dumpable>()
+            .forEach { it.dump(pw, args) }
+    }
+
+    override fun dumpProto(systemUIProtoDump: SystemUIProtoDump, args: Array<String>) {
+        val data =
+            currentTiles.value.map { it.tile.state }.mapNotNull { it.toProto() }.toTypedArray()
+        systemUIProtoDump.tiles = data
+    }
+
+    private fun onCustomTileRemoved(componentName: ComponentName, userId: Int) {
+        val intent = Intent().setComponent(componentName)
+        val lifecycleManager = tileLifecycleManagerFactory.create(intent, UserHandle.of(userId))
+        lifecycleManager.onStopListening()
+        lifecycleManager.onTileRemoved()
+        customTileStatePersister.removeState(TileServiceKey(componentName, userId))
+        customTileAddedRepository.setTileAdded(componentName, userId, false)
+        lifecycleManager.flushMessagesAndUnbind()
+    }
+
+    private suspend fun createTile(spec: TileSpec): QSTile? {
+        val tile = withContext(mainDispatcher) { tileFactory.createTile(spec.spec) }
+        if (tile == null) {
+            logger.logTileNotFoundInFactory(spec)
+            return null
+        } else {
+            tile.tileSpec = spec.spec
+            return if (!tile.isAvailable) {
+                logger.logTileDestroyed(
+                    spec,
+                    QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE,
+                )
+                tile.destroy()
+                null
+            } else {
+                logger.logTileCreated(spec)
+                tile
+            }
+        }
+    }
+
+    private fun processExistingTile(
+        tileSpec: TileSpec,
+        qsTile: QSTile,
+        userChanged: Boolean,
+        user: Int,
+    ): QSTile? {
+        return when {
+            !qsTile.isAvailable -> {
+                logger.logTileDestroyed(
+                    tileSpec,
+                    QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE
+                )
+                qsTile.destroy()
+                null
+            }
+            // Tile is in the current list of tiles and available.
+            // We have a handful of different cases
+            qsTile !is CustomTile -> {
+                // The tile is not a custom tile. Make sure they are reset to the correct user
+                qsTile.removeCallbacks()
+                if (userChanged) {
+                    qsTile.userSwitch(user)
+                    logger.logTileUserChanged(tileSpec, user)
+                }
+                qsTile
+            }
+            qsTile.user == user -> {
+                // The tile is a custom tile for the same user, just return it
+                qsTile.removeCallbacks()
+                qsTile
+            }
+            else -> {
+                // The tile is a custom tile and the user has changed. Destroy it
+                qsTile.destroy()
+                logger.logTileDestroyed(
+                    tileSpec,
+                    QSPipelineLogger.TileDestroyedReason.CUSTOM_TILE_USER_CHANGED
+                )
+                null
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/TileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/TileModel.kt
new file mode 100644
index 0000000..e2381ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/TileModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.model
+
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+/**
+ * Container for a [tile] and its [spec]. The following must be true:
+ * ```
+ * spec.spec == tile.tileSpec
+ * ```
+ */
+data class TileModel(val spec: TileSpec, val tile: QSTile) {
+    init {
+        check(spec.spec == tile.tileSpec)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
index 69d8248..8940800 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
@@ -93,7 +93,7 @@
 
         private fun performRemove(args: List<String>, spec: TileSpec) {
             val user = args.getOrNull(2)?.toInt() ?: userRepository.getSelectedUserInfo().id
-            scope.launch { tileSpecRepository.removeTile(user, spec) }
+            scope.launch { tileSpecRepository.removeTiles(user, listOf(spec)) }
         }
 
         override fun help(pw: PrintWriter) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
index c691c2f..af1cd09 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
@@ -66,6 +66,10 @@
             }
         }
 
+        fun create(component: ComponentName): CustomTileSpec {
+            return CustomTileSpec(CustomTile.toSpec(component), component)
+        }
+
         private val String.isCustomTileSpec: Boolean
             get() = startsWith(CustomTile.PREFIX)
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index 200f743..767ce91 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -73,4 +73,59 @@
             { "Tiles changed in settings for user $int1: $str1" }
         )
     }
+
+    /** Log when a tile is destroyed and its reason for destroying. */
+    fun logTileDestroyed(spec: TileSpec, reason: TileDestroyedReason) {
+        tileListLogBuffer.log(
+            TILE_LIST_TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = spec.toString()
+                str2 = reason.readable
+            },
+            { "Tile $str1 destroyed. Reason: $str2" }
+        )
+    }
+
+    /** Log when a tile is created. */
+    fun logTileCreated(spec: TileSpec) {
+        tileListLogBuffer.log(
+            TILE_LIST_TAG,
+            LogLevel.DEBUG,
+            { str1 = spec.toString() },
+            { "Tile $str1 created" }
+        )
+    }
+
+    /** Ĺog when trying to create a tile, but it's not found in the factory. */
+    fun logTileNotFoundInFactory(spec: TileSpec) {
+        tileListLogBuffer.log(
+            TILE_LIST_TAG,
+            LogLevel.VERBOSE,
+            { str1 = spec.toString() },
+            { "Tile $str1 not found in factory" }
+        )
+    }
+
+    /** Log when the user is changed for a platform tile. */
+    fun logTileUserChanged(spec: TileSpec, user: Int) {
+        tileListLogBuffer.log(
+            TILE_LIST_TAG,
+            LogLevel.VERBOSE,
+            {
+                str1 = spec.toString()
+                int1 = user
+            },
+            { "User changed to $int1 for tile $str1" }
+        )
+    }
+
+    /** Reasons for destroying an existing tile. */
+    enum class TileDestroyedReason(val readable: String) {
+        TILE_REMOVED("Tile removed from  current set"),
+        CUSTOM_TILE_USER_CHANGED("User changed for custom tile"),
+        NEW_TILE_NOT_AVAILABLE("New tile not available"),
+        EXISTING_TILE_NOT_AVAILABLE("Existing tile not available"),
+        TILE_NOT_PRESENT_IN_NEW_USER("Tile not present in new user"),
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index f62e939..cffe45f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.recents;
 
+import static android.content.Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
@@ -32,6 +33,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
@@ -113,6 +115,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
@@ -390,6 +393,16 @@
     private final BroadcastReceiver mLauncherStateChangedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
+            StringBuilder extraComponentList = new StringBuilder(" components: ");
+            if (intent.hasExtra(EXTRA_CHANGED_COMPONENT_NAME_LIST)) {
+                String[] comps = intent.getStringArrayExtra(EXTRA_CHANGED_COMPONENT_NAME_LIST);
+                if (comps != null) {
+                    for (String c : comps) {
+                        extraComponentList.append(c).append(", ");
+                    }
+                }
+            }
+            Log.d(TAG_OPS, "launcherStateChanged intent: " + intent + extraComponentList);
             updateEnabledState();
 
             // Reconnect immediately, instead of waiting for resume to arrive.
@@ -400,9 +413,7 @@
     private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
-            if (SysUiState.DEBUG) {
-                Log.d(TAG_OPS, "Overview proxy service connected");
-            }
+            Log.d(TAG_OPS, "Overview proxy service connected");
             mConnectionBackoffAttempts = 0;
             mHandler.removeCallbacks(mDeferredConnectionCallback);
             try {
@@ -447,6 +458,10 @@
             notifySystemUiStateFlags(mSysUiState.getFlags());
 
             notifyConnectionChanged();
+            if (mLatchForOnUserChanging != null) {
+                mLatchForOnUserChanging.countDown();
+                mLatchForOnUserChanging = null;
+            }
         }
 
         @Override
@@ -501,11 +516,14 @@
         }
     };
 
+    private CountDownLatch mLatchForOnUserChanging;
     private final UserTracker.Callback mUserChangedCallback =
             new UserTracker.Callback() {
                 @Override
-                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                public void onUserChanging(int newUser, @NonNull Context userContext,
+                        CountDownLatch latch) {
                     mConnectionBackoffAttempts = 0;
+                    mLatchForOnUserChanging = latch;
                     internalConnectToCurrentUser("User changed");
                 }
             };
@@ -676,11 +694,14 @@
     }
 
     private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded,
-            boolean bouncerShowing, boolean isDozing, boolean panelExpanded, boolean isDreaming) {
+            boolean keyguardGoingAway, boolean bouncerShowing, boolean isDozing,
+            boolean panelExpanded, boolean isDreaming) {
         mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
                         keyguardShowing && !keyguardOccluded)
                 .setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED,
                         keyguardShowing && keyguardOccluded)
+                .setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY,
+                        keyguardGoingAway)
                 .setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing)
                 .setFlag(SYSUI_STATE_DEVICE_DOZING, isDozing)
                 .setFlag(SYSUI_STATE_DEVICE_DREAMING, isDreaming)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 6f85c45..c9d1da38 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -289,7 +289,7 @@
         if (DEBUG_INPUT) {
             Log.d(TAG, "Predictive Back callback dispatched");
         }
-        respondToBack();
+        respondToKeyDismissal();
     };
 
     private ScreenshotView mScreenshotView;
@@ -581,7 +581,7 @@
         }
     }
 
-    private void respondToBack() {
+    private void respondToKeyDismissal() {
         dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
     }
 
@@ -641,11 +641,11 @@
         mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
 
         mScreenshotView.setOnKeyListener((v, keyCode, event) -> {
-            if (keyCode == KeyEvent.KEYCODE_BACK) {
+            if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
                 if (DEBUG_INPUT) {
-                    Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK");
+                    Log.d(TAG, "onKeyEvent: " + keyCode);
                 }
-                respondToBack();
+                respondToKeyDismissal();
                 return true;
             }
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
index d0b7ad3..3949492 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
@@ -27,9 +27,15 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.os.IBinder;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
 import com.android.internal.statusbar.IAppClipsService;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Application;
@@ -37,6 +43,7 @@
 import com.android.wm.shell.bubbles.Bubbles;
 
 import java.util.Optional;
+import java.util.concurrent.ExecutionException;
 
 import javax.inject.Inject;
 
@@ -46,21 +53,63 @@
  */
 public class AppClipsService extends Service {
 
+    private static final String TAG = AppClipsService.class.getSimpleName();
+
     @Application private final Context mContext;
     private final FeatureFlags mFeatureFlags;
     private final Optional<Bubbles> mOptionalBubbles;
     private final DevicePolicyManager mDevicePolicyManager;
+    private final UserManager mUserManager;
+
     private final boolean mAreTaskAndTimeIndependentPrerequisitesMet;
 
+    @VisibleForTesting()
+    @Nullable ServiceConnector<IAppClipsService> mProxyConnectorToMainProfile;
+
     @Inject
     public AppClipsService(@Application Context context, FeatureFlags featureFlags,
-            Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager) {
+            Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager,
+            UserManager userManager) {
         mContext = context;
         mFeatureFlags = featureFlags;
         mOptionalBubbles = optionalBubbles;
         mDevicePolicyManager = devicePolicyManager;
+        mUserManager = userManager;
+
+        // The consumer of this service are apps that call through StatusBarManager API to query if
+        // it can use app clips API. Since these apps can be launched as work profile users, this
+        // service will start as work profile user. SysUI doesn't share injected instances for
+        // different users. This is why the bubbles instance injected will be incorrect. As the apps
+        // don't generally have permission to connect to a service running as different user, we
+        // start a proxy connection to communicate with the main user's version of this service.
+        if (mUserManager.isManagedProfile()) {
+            // No need to check for prerequisites in this case as those are incorrect for work
+            // profile user instance of the service and the main user version of the service will
+            // take care of this check.
+            mAreTaskAndTimeIndependentPrerequisitesMet = false;
+
+            // Get the main user so that we can connect to the main user's version of the service.
+            UserHandle mainUser = mUserManager.getMainUser();
+            if (mainUser == null) {
+                // If main user is not available there isn't much we can do, no apps can use app
+                // clips.
+                return;
+            }
+
+            // Set up the connection to be used later during onBind callback.
+            mProxyConnectorToMainProfile =
+                    new ServiceConnector.Impl<>(
+                            context,
+                            new Intent(context, AppClipsService.class),
+                            Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
+                                    | Context.BIND_NOT_VISIBLE,
+                            mainUser.getIdentifier(),
+                            IAppClipsService.Stub::asInterface);
+            return;
+        }
 
         mAreTaskAndTimeIndependentPrerequisitesMet = checkIndependentVariables();
+        mProxyConnectorToMainProfile = null;
     }
 
     private boolean checkIndependentVariables() {
@@ -95,6 +144,13 @@
         return new IAppClipsService.Stub() {
             @Override
             public boolean canLaunchCaptureContentActivityForNote(int taskId) {
+                // In case of managed profile, use the main user's instance of the service. Callers
+                // cannot directly connect to the main user's instance as they may not have the
+                // permission to interact across users.
+                if (mUserManager.isManagedProfile()) {
+                    return canLaunchCaptureContentActivityForNoteFromMainUser(taskId);
+                }
+
                 if (!mAreTaskAndTimeIndependentPrerequisitesMet) {
                     return false;
                 }
@@ -107,4 +163,21 @@
             }
         };
     }
+
+    /** Returns whether the app clips API can be used by querying the service as the main user. */
+    private boolean canLaunchCaptureContentActivityForNoteFromMainUser(int taskId) {
+        if (mProxyConnectorToMainProfile == null) {
+            return false;
+        }
+
+        try {
+            AndroidFuture<Boolean> future = mProxyConnectorToMainProfile.postForResult(
+                    service -> service.canLaunchCaptureContentActivityForNote(taskId));
+            return future.get();
+        } catch (ExecutionException | InterruptedException e) {
+            Log.d(TAG, "Exception from service\n" + e);
+        }
+
+        return false;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
index 3cb1a34..0487cbc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
@@ -40,6 +40,8 @@
 import android.os.Handler;
 import android.os.Parcel;
 import android.os.ResultReceiver;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Log;
 
 import androidx.annotation.Nullable;
@@ -79,13 +81,10 @@
 
     private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName();
     static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
-    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-    public static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI";
+    static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI";
     static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE";
-    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-    public static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
-    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-    public static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME";
+    static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
+    static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME";
     private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0);
 
     private final DevicePolicyManager mDevicePolicyManager;
@@ -95,6 +94,7 @@
     private final PackageManager mPackageManager;
     private final UserTracker mUserTracker;
     private final UiEventLogger mUiEventLogger;
+    private final UserManager mUserManager;
     private final ResultReceiver mResultReceiver;
 
     private Intent mKillAppClipsBroadcastIntent;
@@ -103,7 +103,7 @@
     public AppClipsTrampolineActivity(DevicePolicyManager devicePolicyManager, FeatureFlags flags,
             Optional<Bubbles> optionalBubbles, NoteTaskController noteTaskController,
             PackageManager packageManager, UserTracker userTracker, UiEventLogger uiEventLogger,
-            @Main Handler mainHandler) {
+            UserManager userManager, @Main Handler mainHandler) {
         mDevicePolicyManager = devicePolicyManager;
         mFeatureFlags = flags;
         mOptionalBubbles = optionalBubbles;
@@ -111,6 +111,7 @@
         mPackageManager = packageManager;
         mUserTracker = userTracker;
         mUiEventLogger = uiEventLogger;
+        mUserManager = userManager;
 
         mResultReceiver = createResultReceiver(mainHandler);
     }
@@ -123,6 +124,12 @@
             return;
         }
 
+        if (mUserManager.isManagedProfile()) {
+            maybeStartActivityForWPUser();
+            finish();
+            return;
+        }
+
         if (!mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)) {
             finish();
             return;
@@ -191,6 +198,19 @@
         }
     }
 
+    private void maybeStartActivityForWPUser() {
+        UserHandle mainUser = mUserManager.getMainUser();
+        if (mainUser == null) {
+            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+            return;
+        }
+
+        // Start the activity as the main user with activity result forwarding.
+        startActivityAsUser(
+                new Intent(this, AppClipsTrampolineActivity.class)
+                        .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), mainUser);
+    }
+
     private void setErrorResultAndFinish(int errorCode) {
         setResult(RESULT_OK,
                 new Intent().putExtra(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, errorCode));
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 72286f1..3711a2f 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -162,7 +162,7 @@
     private fun registerUserSwitchObserver() {
         iActivityManager.registerUserSwitchObserver(object : UserSwitchObserver() {
             override fun onBeforeUserSwitching(newUserId: Int) {
-                setUserIdInternal(newUserId)
+                handleBeforeUserSwitching(newUserId)
             }
 
             override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) {
@@ -180,6 +180,10 @@
         }, TAG)
     }
 
+    protected open fun handleBeforeUserSwitching(newUserId: Int) {
+        setUserIdInternal(newUserId)
+    }
+
     @WorkerThread
     protected open fun handleUserSwitching(newUserId: Int) {
         Assert.isNotMainThread()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
index 754036d..b8bd95c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
@@ -14,9 +14,9 @@
 package com.android.systemui.shade
 
 import android.view.MotionEvent
+import com.android.systemui.common.buffer.RingBuffer
 import com.android.systemui.dump.DumpsysTableLogger
 import com.android.systemui.dump.Row
-import com.android.systemui.plugins.util.RingBuffer
 import java.text.SimpleDateFormat
 import java.util.Locale
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 79d3b26..aedd976 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -22,10 +22,6 @@
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
-import static androidx.constraintlayout.widget.ConstraintSet.END;
-import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
-
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION;
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
 import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
@@ -73,12 +69,6 @@
 import android.os.UserManager;
 import android.os.VibrationEffect;
 import android.provider.Settings;
-import android.transition.ChangeBounds;
-import android.transition.Transition;
-import android.transition.TransitionListenerAdapter;
-import android.transition.TransitionManager;
-import android.transition.TransitionSet;
-import android.transition.TransitionValues;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.MathUtils;
@@ -100,8 +90,6 @@
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 
-import androidx.constraintlayout.widget.ConstraintSet;
-
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
@@ -162,7 +150,7 @@
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.plugins.ClockController;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
 import com.android.systemui.plugins.qs.QS;
@@ -299,11 +287,6 @@
     private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
     private static final Rect EMPTY_RECT = new Rect();
     /**
-     * Duration to use for the animator when the keyguard status view alignment changes, and a
-     * custom clock animation is in use.
-     */
-    private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;
-    /**
      * Whether the Shade should animate to reflect Back gesture progress.
      * To minimize latency at runtime, we cache this, else we'd be reading it every time
      * updateQsExpansion() is called... and it's called very often.
@@ -550,8 +533,6 @@
 
     private final KeyguardMediaController mKeyguardMediaController;
 
-    private boolean mStatusViewCentered = true;
-
     private final Optional<KeyguardUnfoldTransition> mKeyguardUnfoldTransition;
     private final Optional<NotificationPanelUnfoldAnimationController>
             mNotificationPanelUnfoldAnimationController;
@@ -682,35 +663,29 @@
                     step.getTransitionState() == TransitionState.RUNNING;
             };
 
-    private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
-            new TransitionListenerAdapter() {
-                @Override
-                public void onTransitionCancel(Transition transition) {
-                    mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
-                }
-
-                @Override
-                public void onTransitionEnd(Transition transition) {
-                    mInteractionJankMonitor.end(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
-                }
-            };
+    private final ActivityStarter mActivityStarter;
 
     @Inject
     public NotificationPanelViewController(NotificationPanelView view,
             @Main Handler handler,
             LayoutInflater layoutInflater,
             FeatureFlags featureFlags,
-            NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler,
+            NotificationWakeUpCoordinator coordinator,
+            PulseExpansionHandler pulseExpansionHandler,
             DynamicPrivacyController dynamicPrivacyController,
-            KeyguardBypassController bypassController, FalsingManager falsingManager,
+            KeyguardBypassController bypassController,
+            FalsingManager falsingManager,
             FalsingCollector falsingCollector,
             KeyguardStateController keyguardStateController,
             StatusBarStateController statusBarStateController,
             StatusBarWindowStateController statusBarWindowStateController,
             NotificationShadeWindowController notificationShadeWindowController,
             DozeLog dozeLog,
-            DozeParameters dozeParameters, CommandQueue commandQueue, VibratorHelper vibratorHelper,
-            LatencyTracker latencyTracker, PowerManager powerManager,
+            DozeParameters dozeParameters,
+            CommandQueue commandQueue,
+            VibratorHelper vibratorHelper,
+            LatencyTracker latencyTracker,
+            PowerManager powerManager,
             AccessibilityManager accessibilityManager, @DisplayId int displayId,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             MetricsLogger metricsLogger,
@@ -771,7 +746,8 @@
             Provider<MultiShadeInteractor> multiShadeInteractorProvider,
             DumpManager dumpManager,
             KeyguardLongPressViewModel keyguardLongPressViewModel,
-            KeyguardInteractor keyguardInteractor) {
+            KeyguardInteractor keyguardInteractor,
+            ActivityStarter activityStarter) {
         mInteractionJankMonitor = interactionJankMonitor;
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
@@ -952,6 +928,7 @@
                     return Unit.INSTANCE;
                 },
                 mFalsingManager);
+        mActivityStarter = activityStarter;
         onFinishInflate();
         keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
                 new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
@@ -1313,9 +1290,6 @@
         keyguardStatusView = (KeyguardStatusView) mLayoutInflater.inflate(
                 R.layout.keyguard_status_view, mNotificationContainerParent, false);
         mNotificationContainerParent.addView(keyguardStatusView, statusIndex);
-        // When it's reinflated, this is centered by default. If it shouldn't be, this will update
-        // below when resources are updated.
-        mStatusViewCentered = true;
         attachSplitShadeMediaPlayerContainer(
                 keyguardStatusView.findViewById(R.id.status_view_media_container));
 
@@ -1394,7 +1368,8 @@
                 mLockIconViewController,
                 stringResourceId ->
                         mKeyguardIndicationController.showTransientIndication(stringResourceId),
-                mVibratorHelper);
+                mVibratorHelper,
+                mActivityStarter);
     }
 
     @VisibleForTesting
@@ -1609,68 +1584,9 @@
 
     private void updateKeyguardStatusViewAlignment(boolean animate) {
         boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
-        if (mStatusViewCentered != shouldBeCentered) {
-            mStatusViewCentered = shouldBeCentered;
-            ConstraintSet constraintSet = new ConstraintSet();
-            constraintSet.clone(mNotificationContainerParent);
-            int statusConstraint = shouldBeCentered ? PARENT_ID : R.id.qs_edge_guideline;
-            constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END);
-            if (animate) {
-                mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
-                ChangeBounds transition = new ChangeBounds();
-                if (mSplitShadeEnabled) {
-                    // Excluding media from the transition on split-shade, as it doesn't transition
-                    // horizontally properly.
-                    transition.excludeTarget(R.id.status_view_media_container, true);
-                }
-
-                transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-                transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-
-                ClockController clock = mKeyguardStatusViewController.getClockController();
-                boolean customClockAnimation = clock != null
-                        && clock.getConfig().getHasCustomPositionUpdatedAnimation();
-
-                if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
-                    // Find the clock, so we can exclude it from this transition.
-                    FrameLayout clockContainerView =
-                            mView.findViewById(R.id.lockscreen_clock_view_large);
-
-                    // The clock container can sometimes be null. If it is, just fall back to the
-                    // old animation rather than setting up the custom animations.
-                    if (clockContainerView == null || clockContainerView.getChildCount() == 0) {
-                        transition.addListener(mKeyguardStatusAlignmentTransitionListener);
-                        TransitionManager.beginDelayedTransition(
-                                mNotificationContainerParent, transition);
-                    } else {
-                        View clockView = clockContainerView.getChildAt(0);
-
-                        transition.excludeTarget(clockView, /* exclude= */ true);
-
-                        TransitionSet set = new TransitionSet();
-                        set.addTransition(transition);
-
-                        SplitShadeTransitionAdapter adapter =
-                                new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
-
-                        // Use linear here, so the actual clock can pick its own interpolator.
-                        adapter.setInterpolator(Interpolators.LINEAR);
-                        adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION);
-                        adapter.addTarget(clockView);
-                        set.addTransition(adapter);
-                        set.addListener(mKeyguardStatusAlignmentTransitionListener);
-                        TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
-                    }
-                } else {
-                    transition.addListener(mKeyguardStatusAlignmentTransitionListener);
-                    TransitionManager.beginDelayedTransition(
-                            mNotificationContainerParent, transition);
-                }
-            }
-
-            constraintSet.applyTo(mNotificationContainerParent);
-        }
-        mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(mStatusViewCentered));
+        mKeyguardStatusViewController.updateAlignment(
+                mNotificationContainerParent, mSplitShadeEnabled, shouldBeCentered, animate);
+        mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered));
     }
 
     private boolean shouldKeyguardStatusViewBeCentered() {
@@ -3324,7 +3240,6 @@
         ipw.print("mIsGestureNavigation="); ipw.println(mIsGestureNavigation);
         ipw.print("mOldLayoutDirection="); ipw.println(mOldLayoutDirection);
         ipw.print("mMinFraction="); ipw.println(mMinFraction);
-        ipw.print("mStatusViewCentered="); ipw.println(mStatusViewCentered);
         ipw.print("mSplitShadeFullTransitionDistance=");
         ipw.println(mSplitShadeFullTransitionDistance);
         ipw.print("mSplitShadeScrimTransitionDistance=");
@@ -4925,6 +4840,7 @@
             }
 
             handled |= handleTouch(event);
+            mShadeLog.logOnTouchEventLastReturn(event, !mDozing, handled);
             return !mDozing || handled;
         }
 
@@ -5107,6 +5023,7 @@
                     }
                     break;
             }
+            mShadeLog.logHandleTouchLastReturn(event, !mGestureWaitForTouchSlop, mTracking);
             return !mGestureWaitForTouchSlop || mTracking;
         }
 
@@ -5117,65 +5034,6 @@
         }
     }
 
-    static class SplitShadeTransitionAdapter extends Transition {
-        private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds";
-        private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS };
-
-        private final KeyguardStatusViewController mController;
-
-        SplitShadeTransitionAdapter(KeyguardStatusViewController controller) {
-            mController = controller;
-        }
-
-        private void captureValues(TransitionValues transitionValues) {
-            Rect boundsRect = new Rect();
-            boundsRect.left = transitionValues.view.getLeft();
-            boundsRect.top = transitionValues.view.getTop();
-            boundsRect.right = transitionValues.view.getRight();
-            boundsRect.bottom = transitionValues.view.getBottom();
-            transitionValues.values.put(PROP_BOUNDS, boundsRect);
-        }
-
-        @Override
-        public void captureEndValues(TransitionValues transitionValues) {
-            captureValues(transitionValues);
-        }
-
-        @Override
-        public void captureStartValues(TransitionValues transitionValues) {
-            captureValues(transitionValues);
-        }
-
-        @Nullable
-        @Override
-        public Animator createAnimator(ViewGroup sceneRoot, @Nullable TransitionValues startValues,
-                @Nullable TransitionValues endValues) {
-            if (startValues == null || endValues == null) {
-                return null;
-            }
-            ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
-
-            Rect from = (Rect) startValues.values.get(PROP_BOUNDS);
-            Rect to = (Rect) endValues.values.get(PROP_BOUNDS);
-
-            anim.addUpdateListener(animation -> {
-                ClockController clock = mController.getClockController();
-                if (clock == null) {
-                    return;
-                }
-
-                clock.getAnimations().onPositionUpdated(from, to, animation.getAnimatedFraction());
-            });
-
-            return anim;
-        }
-
-        @Override
-        public String[] getTransitionProperties() {
-            return TRANSITION_PROPERTIES;
-        }
-    }
-
     private final class HeadsUpNotificationViewControllerImpl implements
             HeadsUpTouchHelper.HeadsUpNotificationViewController {
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 156e4fd..af74c27 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -561,6 +561,7 @@
         for (StatusBarWindowCallback cb : activeCallbacks) {
             cb.onStateChanged(mCurrentState.keyguardShowing,
                     mCurrentState.keyguardOccluded,
+                    mCurrentState.keyguardGoingAway,
                     mCurrentState.bouncerShowing,
                     mCurrentState.dozing,
                     mCurrentState.panelExpanded,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index fed9b84..7812f07 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.shade
 
+import com.android.systemui.common.buffer.RingBuffer
 import com.android.systemui.dump.DumpsysTableLogger
 import com.android.systemui.dump.Row
-import com.android.systemui.plugins.util.RingBuffer
 import com.android.systemui.shade.NotificationShadeWindowState.Buffer
 import com.android.systemui.statusbar.StatusBarState
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index b31ec33..7cb1cbe 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -17,6 +17,8 @@
 
 package com.android.systemui.shade;
 
+import static android.view.WindowInsets.Type.ime;
+
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
 import static com.android.systemui.shade.NotificationPanelViewController.COUNTER_PANEL_OPEN_QS;
@@ -463,9 +465,17 @@
         return (mQs != null ? mQs.getHeader().getHeight() : 0) + mPeekHeight;
     }
 
+    private boolean isRemoteInputActiveWithKeyboardUp() {
+        //TODO(b/227115380) remove the isVisible(ime()) check once isRemoteInputActive is fixed.
+        // The check for keyboard visibility is a temporary workaround that allows QS to expand
+        // even when isRemoteInputActive is mistakenly returning true.
+        return mRemoteInputManager.isRemoteInputActive()
+                && mPanelView.getRootWindowInsets().isVisible(ime());
+    }
+
     public boolean isExpansionEnabled() {
         return mExpansionEnabledPolicy && mExpansionEnabledAmbient
-                && !mRemoteInputManager.isRemoteInputActive();
+            && !isRemoteInputActiveWithKeyboardUp();
     }
 
     public float getTransitioningToFullShadeProgress() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index b79f32a..b4653be 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -43,13 +43,13 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.qs.ChipVisibilityListener
 import com.android.systemui.qs.HeaderPrivacyIconsController
-import com.android.systemui.qs.carrier.QSCarrierGroup
-import com.android.systemui.qs.carrier.QSCarrierGroupController
 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
 import com.android.systemui.shade.ShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
 import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
+import com.android.systemui.shade.carrier.ShadeCarrierGroup
+import com.android.systemui.shade.carrier.ShadeCarrierGroupController
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusBarLocation
@@ -87,7 +87,7 @@
     private val variableDateViewControllerFactory: VariableDateViewController.Factory,
     @Named(SHADE_HEADER) private val batteryMeterViewController: BatteryMeterViewController,
     private val dumpManager: DumpManager,
-    private val qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
+    private val shadeCarrierGroupControllerBuilder: ShadeCarrierGroupController.Builder,
     private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager,
     private val demoModeController: DemoModeController,
     private val qsBatteryModeController: QsBatteryModeController,
@@ -114,13 +114,13 @@
 
     private lateinit var iconManager: StatusBarIconController.TintedIconManager
     private lateinit var carrierIconSlots: List<String>
-    private lateinit var qsCarrierGroupController: QSCarrierGroupController
+    private lateinit var mShadeCarrierGroupController: ShadeCarrierGroupController
 
     private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
     private val clock: Clock = header.findViewById(R.id.clock)
     private val date: TextView = header.findViewById(R.id.date)
     private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
-    private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group)
+    private val mShadeCarrierGroup: ShadeCarrierGroup = header.findViewById(R.id.carrier_group)
 
     private var roundedCorners = 0
     private var cutout: DisplayCutout? = null
@@ -243,7 +243,7 @@
             override fun onDensityOrFontScaleChanged() {
                 clock.setTextAppearance(R.style.TextAppearance_QS_Status)
                 date.setTextAppearance(R.style.TextAppearance_QS_Status)
-                qsCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers)
+                mShadeCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers)
                 loadConstraints()
                 header.minHeight =
                     resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height)
@@ -266,8 +266,8 @@
 
         carrierIconSlots =
             listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile))
-        qsCarrierGroupController =
-            qsCarrierGroupControllerBuilder.setQSCarrierGroup(qsCarrierGroup).build()
+        mShadeCarrierGroupController =
+            shadeCarrierGroupControllerBuilder.setShadeCarrierGroup(mShadeCarrierGroup).build()
 
         privacyIconsController.onParentVisible()
     }
@@ -284,7 +284,7 @@
             v.pivotX = newPivot
             v.pivotY = v.height.toFloat() / 2
 
-            qsCarrierGroup.setPaddingRelative((v.width * v.scaleX).toInt(), 0, 0, 0)
+            mShadeCarrierGroup.setPaddingRelative((v.width * v.scaleX).toInt(), 0, 0, 0)
         }
 
         dumpManager.registerDumpable(this)
@@ -439,12 +439,14 @@
     }
 
     private fun updateListeners() {
-        qsCarrierGroupController.setListening(visible)
+        mShadeCarrierGroupController.setListening(visible)
         if (visible) {
-            updateSingleCarrier(qsCarrierGroupController.isSingleCarrier)
-            qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) }
+            updateSingleCarrier(mShadeCarrierGroupController.isSingleCarrier)
+            mShadeCarrierGroupController.setOnSingleCarrierChangedListener {
+                updateSingleCarrier(it)
+            }
         } else {
-            qsCarrierGroupController.setOnSingleCarrierChangedListener(null)
+            mShadeCarrierGroupController.setOnSingleCarrierChangedListener(null)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index da4944c..a931838 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -316,4 +316,80 @@
             { "QSC NotificationsClippingTopBound set to $int1 - $int2" }
         )
     }
+
+    fun logOnTouchEventLastReturn(
+        event: MotionEvent,
+        dozing: Boolean,
+        handled: Boolean,
+    ) {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                bool1 = dozing
+                bool2 = handled
+                long1 = event.eventTime
+                long2 = event.downTime
+                int1 = event.action
+                int2 = event.classification
+                double1 = event.y.toDouble()
+            },
+            {
+                "NPVC onTouchEvent last return: !mDozing: $bool1 || handled: $bool2 " +
+                        "\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2"
+            }
+        )
+    }
+
+    fun logHandleTouchLastReturn(
+        event: MotionEvent,
+        gestureWaitForTouchSlop: Boolean,
+        tracking: Boolean,
+    ) {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                bool1 = gestureWaitForTouchSlop
+                bool2 = tracking
+                long1 = event.eventTime
+                long2 = event.downTime
+                int1 = event.action
+                int2 = event.classification
+                double1 = event.y.toDouble()
+            },
+            {
+                "NPVC handleTouch last return: !mGestureWaitForTouchSlop: $bool1 " +
+                        "|| mTracking: $bool2 " +
+                        "\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2"
+            }
+        )
+    }
+
+    fun logUpdateNotificationPanelTouchState(
+        disabled: Boolean,
+        isGoingToSleep: Boolean,
+        shouldControlScreenOff: Boolean,
+        deviceInteractive: Boolean,
+        isPulsing: Boolean,
+        isFrpActive: Boolean,
+    ) {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                bool1 = disabled
+                bool2 = isGoingToSleep
+                bool3 = shouldControlScreenOff
+                bool4 = deviceInteractive
+                str1 = isPulsing.toString()
+                str2 = isFrpActive.toString()
+            },
+            {
+                "CentralSurfaces updateNotificationPanelTouchState set disabled to: $bool1\n" +
+                        "isGoingToSleep: $bool2, !shouldControlScreenOff: $bool3," +
+                        "!mDeviceInteractive: $bool4, !isPulsing: $str1, isFrpActive: $str2"
+            }
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt b/packages/SystemUI/src/com/android/systemui/shade/carrier/CellSignalState.kt
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt
rename to packages/SystemUI/src/com/android/systemui/shade/carrier/CellSignalState.kt
index e925b54..958230b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/CellSignalState.kt
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2020 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.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.carrier
+package com.android.systemui.shade.carrier
 
 /**
  * Represents the state of cell signal for a particular slot.
  *
- * To be used between [QSCarrierGroupController] and [QSCarrier].
+ * To be used between [ShadeCarrierGroupController] and [ShadeCarrier].
  */
 data class CellSignalState(
     @JvmField val visible: Boolean = false,
@@ -37,7 +37,6 @@
      * @return `this` if `this.visible == visible`. Else, a new copy with the visibility changed.
      */
     fun changeVisibility(visible: Boolean): CellSignalState {
-        if (this.visible == visible) return this
-        else return copy(visible = visible)
+        if (this.visible == visible) return this else return copy(visible = visible)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrier.java
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
rename to packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrier.java
index b5ceeae..8586828 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrier.java
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2020 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.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.carrier;
+package com.android.systemui.shade.carrier;
 
 import android.annotation.StyleRes;
 import android.content.Context;
@@ -38,7 +38,7 @@
 
 import java.util.Objects;
 
-public class QSCarrier extends LinearLayout {
+public class ShadeCarrier extends LinearLayout {
 
     private View mMobileGroup;
     private TextView mCarrierText;
@@ -50,19 +50,19 @@
     private boolean mMobileSignalInitialized = false;
     private boolean mIsSingleCarrier;
 
-    public QSCarrier(Context context) {
+    public ShadeCarrier(Context context) {
         super(context);
     }
 
-    public QSCarrier(Context context, AttributeSet attrs) {
+    public ShadeCarrier(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
-    public QSCarrier(Context context, AttributeSet attrs, int defStyleAttr) {
+    public ShadeCarrier(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
     }
 
-    public QSCarrier(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+    public ShadeCarrier(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
     }
 
@@ -72,7 +72,7 @@
         mMobileGroup = findViewById(R.id.mobile_combo);
         mMobileRoaming = findViewById(R.id.mobile_roaming);
         mMobileSignal = findViewById(R.id.mobile_signal);
-        mCarrierText = findViewById(R.id.qs_carrier_text);
+        mCarrierText = findViewById(R.id.shade_carrier_text);
         mSpacer = findViewById(R.id.spacer);
         updateResources();
     }
@@ -158,7 +158,7 @@
         mCarrierText.setMaxEms(
                 useLargeScreenHeader
                         ? Integer.MAX_VALUE
-                        : getResources().getInteger(R.integer.qs_carrier_max_em)
+                        : getResources().getInteger(R.integer.shade_carrier_max_em)
         );
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroup.java
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java
rename to packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroup.java
index a36035b..68561d1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroup.java
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2020 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.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.carrier;
+package com.android.systemui.shade.carrier;
 
 import android.annotation.StyleRes;
 import android.content.Context;
@@ -27,10 +27,10 @@
 import com.android.systemui.R;
 
 /**
- * Displays Carrier name and network status in QS
+ * Displays Carrier name and network status in the shade header
  */
-public class QSCarrierGroup extends LinearLayout {
-    public QSCarrierGroup(Context context, AttributeSet attrs) {
+public class ShadeCarrierGroup extends LinearLayout {
+    public ShadeCarrierGroup(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
@@ -38,24 +38,24 @@
         return findViewById(R.id.no_carrier_text);
     }
 
-    QSCarrier getCarrier1View() {
+    ShadeCarrier getCarrier1View() {
         return findViewById(R.id.carrier1);
     }
 
-    QSCarrier getCarrier2View() {
+    ShadeCarrier getCarrier2View() {
         return findViewById(R.id.carrier2);
     }
 
-    QSCarrier getCarrier3View() {
+    ShadeCarrier getCarrier3View() {
         return findViewById(R.id.carrier3);
     }
 
     View getCarrierDivider1() {
-        return findViewById(R.id.qs_carrier_divider1);
+        return findViewById(R.id.shade_carrier_divider1);
     }
 
     View getCarrierDivider2() {
-        return findViewById(R.id.qs_carrier_divider2);
+        return findViewById(R.id.shade_carrier_divider2);
     }
 
     public void updateTextAppearance(@StyleRes int resId) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
rename to packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
index 6a8bf75..0ebcfa2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2020 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.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.carrier;
+package com.android.systemui.shade.carrier;
 
 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
 
@@ -52,8 +52,8 @@
 
 import javax.inject.Inject;
 
-public class QSCarrierGroupController {
-    private static final String TAG = "QSCarrierGroup";
+public class ShadeCarrierGroupController {
+    private static final String TAG = "ShadeCarrierGroup";
 
     /**
      * Support up to 3 slots which is what's supported by {@link TelephonyManager#getPhoneCount}
@@ -72,7 +72,7 @@
     private final CellSignalState[] mInfos =
             new CellSignalState[SIM_SLOTS];
     private View[] mCarrierDividers = new View[SIM_SLOTS - 1];
-    private QSCarrier[] mCarrierGroups = new QSCarrier[SIM_SLOTS];
+    private ShadeCarrier[] mCarrierGroups = new ShadeCarrier[SIM_SLOTS];
     private int[] mLastSignalLevel = new int[SIM_SLOTS];
     private String[] mLastSignalLevelDescription = new String[SIM_SLOTS];
     private final CarrierConfigTracker mCarrierConfigTracker;
@@ -129,7 +129,7 @@
         }
     }
 
-    private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter,
+    private ShadeCarrierGroupController(ShadeCarrierGroup view, ActivityStarter activityStarter,
             @Background Handler bgHandler, @Main Looper mainLooper,
             NetworkController networkController,
             CarrierTextManager.Builder carrierTextManagerBuilder, Context context,
@@ -167,7 +167,7 @@
         for (int i = 0; i < SIM_SLOTS; i++) {
             mInfos[i] = new CellSignalState(
                     true,
-                    R.drawable.ic_qs_no_calling_sms,
+                    R.drawable.ic_shade_no_calling_sms,
                     context.getText(AccessibilityContentDescriptions.NO_CALLING).toString(),
                     "",
                     false);
@@ -257,7 +257,7 @@
         if (singleCarrier) {
             for (int i = 0; i < SIM_SLOTS; i++) {
                 if (mInfos[i].visible
-                        && mInfos[i].mobileSignalIconId == R.drawable.ic_qs_sim_card) {
+                        && mInfos[i].mobileSignalIconId == R.drawable.ic_shade_sim_card) {
                     mInfos[i] = new CellSignalState(true, R.drawable.ic_blank, "", "", false);
                 }
             }
@@ -322,8 +322,8 @@
                 Log.e(TAG, "Carrier information arrays not of same length");
             }
         } else {
-            // No sims or airplane mode (but not WFC). Do not show QSCarrierGroup, instead just show
-            // info.carrierText in a different view.
+            // No sims or airplane mode (but not WFC). Do not show ShadeCarrierGroup,
+            // instead just show info.carrierText in a different view.
             for (int i = 0; i < SIM_SLOTS; i++) {
                 mInfos[i] = mInfos[i].changeVisibility(false);
                 mCarrierGroups[i].setCarrierText("");
@@ -368,7 +368,7 @@
     }
 
     public static class Builder {
-        private QSCarrierGroup mView;
+        private ShadeCarrierGroup mView;
         private final ActivityStarter mActivityStarter;
         private final Handler mHandler;
         private final Looper mLooper;
@@ -393,13 +393,13 @@
             mSlotIndexResolver = slotIndexResolver;
         }
 
-        public Builder setQSCarrierGroup(QSCarrierGroup view) {
+        public Builder setShadeCarrierGroup(ShadeCarrierGroup view) {
             mView = view;
             return this;
         }
 
-        public QSCarrierGroupController build() {
-            return new QSCarrierGroupController(mView, mActivityStarter, mHandler, mLooper,
+        public ShadeCarrierGroupController build() {
+            return new ShadeCarrierGroupController(mView, mActivityStarter, mHandler, mLooper,
                     mNetworkController, mCarrierTextControllerBuilder, mContext,
                     mCarrierConfigTracker, mSlotIndexResolver);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index c84894f..06f43f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -841,6 +841,19 @@
         BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
         behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
         behavior.setSkipCollapsed(true);
+        behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
+                    @Override
+                    public void onStateChanged(@NonNull View bottomSheet, int newState) {
+                        if (newState == BottomSheetBehavior.STATE_DRAGGING) {
+                            behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
+                        }
+                    }
+
+                    @Override
+                    public void onSlide(@NonNull View bottomSheet, float slideOffset) {
+                        // Do nothing.
+                    }
+                });
 
         mKeyboardShortcutsBottomSheetDialog.setCanceledOnTouchOutside(true);
         Window keyboardShortcutsWindow = mKeyboardShortcutsBottomSheetDialog.getWindow();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 765c93e..142689e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -1127,13 +1127,7 @@
             final boolean faceAuthUnavailable = biometricSourceType == FACE
                     && msgId == BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
 
-            // TODO(b/141025588): refactor to reduce repetition of code/comments
-            // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
-            // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
-            // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
-            // check of whether non-strong biometric is allowed
-            if (!mKeyguardUpdateMonitor
-                    .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
+            if (isPrimaryAuthRequired()
                     && !faceAuthUnavailable) {
                 return;
             }
@@ -1234,7 +1228,7 @@
         private void onFaceAuthError(int msgId, String errString) {
             CharSequence deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
             mFaceAcquiredMessageDeferral.reset();
-            if (shouldSuppressFaceError(msgId, mKeyguardUpdateMonitor)) {
+            if (shouldSuppressFaceError(msgId)) {
                 mKeyguardLogger.logBiometricMessage("suppressingFaceError", msgId, errString);
                 return;
             }
@@ -1248,7 +1242,7 @@
         }
 
         private void onFingerprintAuthError(int msgId, String errString) {
-            if (shouldSuppressFingerprintError(msgId, mKeyguardUpdateMonitor)) {
+            if (shouldSuppressFingerprintError(msgId)) {
                 mKeyguardLogger.logBiometricMessage("suppressingFingerprintError",
                         msgId,
                         errString);
@@ -1257,31 +1251,19 @@
             }
         }
 
-        private boolean shouldSuppressFingerprintError(int msgId,
-                KeyguardUpdateMonitor updateMonitor) {
-            // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
-            // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
-            // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
-            // check of whether non-strong biometric is allowed
-            return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
-                    && !isLockoutError(msgId))
+        private boolean shouldSuppressFingerprintError(int msgId) {
+            return ((isPrimaryAuthRequired() && !isLockoutError(msgId))
                     || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
                     || msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED
                     || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED);
         }
 
-        private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) {
-            // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
-            // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
-            // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
-            // check of whether non-strong biometric is allowed
-            return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
-                    && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT)
+        private boolean shouldSuppressFaceError(int msgId) {
+            return ((isPrimaryAuthRequired() && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT)
                     || msgId == FaceManager.FACE_ERROR_CANCELED
                     || msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS);
         }
 
-
         @Override
         public void onTrustChanged(int userId) {
             if (!isCurrentUser(userId)) return;
@@ -1355,6 +1337,16 @@
         }
     }
 
+    private boolean isPrimaryAuthRequired() {
+        // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+        // as long as primary auth, i.e. PIN/pattern/password, is required), so it's ok to
+        // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+        // check of whether non-strong biometric is allowed since strong biometrics can still be
+        // used.
+        return !mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                true /* isStrongBiometric */);
+    }
+
     protected boolean isPluggedInAndCharging() {
         return mPowerPluggedIn;
     }
@@ -1431,7 +1423,7 @@
 
     private boolean canUnlockWithFingerprint() {
         return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser());
+                getCurrentUser()) && mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed();
     }
 
     private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java
index cb4ae28..f7d37e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java
@@ -25,7 +25,6 @@
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
 
@@ -35,7 +34,7 @@
  * Controller class for {@link NotificationShelf}.
  */
 @NotificationRowScope
-public class NotificationShelfController {
+public class LegacyNotificationShelfControllerImpl implements NotificationShelfController {
     private final NotificationShelf mView;
     private final ActivatableNotificationViewController mActivatableNotificationViewController;
     private final KeyguardBypassController mKeyguardBypassController;
@@ -44,7 +43,7 @@
     private AmbientState mAmbientState;
 
     @Inject
-    public NotificationShelfController(
+    public LegacyNotificationShelfControllerImpl(
             NotificationShelf notificationShelf,
             ActivatableNotificationViewController activatableNotificationViewController,
             KeyguardBypassController keyguardBypassController,
@@ -79,56 +78,42 @@
         }
     }
 
+    @Override
     public NotificationShelf getView() {
         return mView;
     }
 
+    @Override
     public boolean canModifyColorOfNotifications() {
         return mAmbientState.isShadeExpanded()
                 && !(mAmbientState.isOnKeyguard() && mKeyguardBypassController.getBypassEnabled());
     }
 
+    @Override
     public NotificationIconContainer getShelfIcons() {
         return mView.getShelfIcons();
     }
 
-    public @View.Visibility int getVisibility() {
-        return mView.getVisibility();
-    }
-
-    public void setCollapsedIcons(NotificationIconContainer notificationIcons) {
-        mView.setCollapsedIcons(notificationIcons);
-    }
-
+    @Override
     public void bind(AmbientState ambientState,
             NotificationStackScrollLayoutController notificationStackScrollLayoutController) {
         mView.bind(ambientState, notificationStackScrollLayoutController);
         mAmbientState = ambientState;
     }
 
-    public int getHeight() {
-        return mView.getHeight();
-    }
-
-    public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
-        mAmbientState = ambientState;
-        mView.updateState(algorithmState, ambientState);
-    }
-
+    @Override
     public int getIntrinsicHeight() {
         return mView.getIntrinsicHeight();
     }
 
+    @Override
     public void setOnActivatedListener(ActivatableNotificationView.OnActivatedListener listener) {
         mView.setOnActivatedListener(listener);
     }
 
+    @Override
     public void setOnClickListener(View.OnClickListener onClickListener) {
         mView.setOnClickListener(onClickListener);
     }
 
-    public int getNotGoneIndex() {
-        return mView.getNotGoneIndex();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 4873c9d..d1c6aef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -24,6 +24,7 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.IndentingPrintWriter;
+import android.util.Log;
 import android.util.MathUtils;
 import android.view.View;
 import android.view.ViewGroup;
@@ -64,8 +65,7 @@
  * A notification shelf view that is placed inside the notification scroller. It manages the
  * overflow icons that don't fit into the regular list anymore.
  */
-public class NotificationShelf extends ActivatableNotificationView implements
-        View.OnLayoutChangeListener, StateListener {
+public class NotificationShelf extends ActivatableNotificationView implements StateListener {
 
     private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag;
     private static final String TAG = "NotificationShelf";
@@ -78,7 +78,6 @@
     private static final SourceType SHELF_SCROLL = SourceType.from("ShelfScroll");
 
     private NotificationIconContainer mShelfIcons;
-    private int[] mTmp = new int[2];
     private boolean mHideBackground;
     private int mStatusBarHeight;
     private boolean mEnableNotificationClipping;
@@ -87,7 +86,6 @@
     private int mPaddingBetweenElements;
     private int mNotGoneIndex;
     private boolean mHasItemsInStableShelf;
-    private NotificationIconContainer mCollapsedIcons;
     private int mScrollFastThreshold;
     private int mStatusBarState;
     private boolean mInteractive;
@@ -99,6 +97,8 @@
     private NotificationShelfController mController;
     private float mActualWidth = -1;
     private boolean mSensitiveRevealAnimEndabled;
+    private boolean mShelfRefactorFlagEnabled;
+    private boolean mCanModifyColorOfNotifications;
 
     public NotificationShelf(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -428,7 +428,7 @@
                     transitionAmount = inShelfAmount;
                 }
                 // We don't want to modify the color if the notification is hun'd
-                if (isLastChild && mController.canModifyColorOfNotifications()) {
+                if (isLastChild && canModifyColorOfNotifications()) {
                     if (colorOfViewBeforeLast == NO_COLOR) {
                         colorOfViewBeforeLast = ownColorUntinted;
                     }
@@ -493,6 +493,14 @@
         }
     }
 
+    private boolean canModifyColorOfNotifications() {
+        if (mShelfRefactorFlagEnabled) {
+            return mCanModifyColorOfNotifications && mAmbientState.isShadeExpanded();
+        } else {
+            return mController.canModifyColorOfNotifications();
+        }
+    }
+
     private void updateCornerRoundnessOnScroll(
             ActivatableNotificationView anv,
             float viewStart,
@@ -868,10 +876,6 @@
         return mShelfIcons.getIconState(icon);
     }
 
-    private float getFullyClosedTranslation() {
-        return -(getIntrinsicHeight() - mStatusBarHeight) / 2;
-    }
-
     @Override
     public boolean hasNoContentHeight() {
         return true;
@@ -893,7 +897,6 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
-        updateRelativeOffset();
 
         // we always want to clip to our sides, such that nothing can draw outside of these bounds
         int height = getResources().getDisplayMetrics().heightPixels;
@@ -903,13 +906,6 @@
         }
     }
 
-    private void updateRelativeOffset() {
-        if (mCollapsedIcons != null) {
-            mCollapsedIcons.getLocationOnScreen(mTmp);
-        }
-        getLocationOnScreen(mTmp);
-    }
-
     /**
      * @return the index of the notification at which the shelf visually resides
      */
@@ -924,19 +920,6 @@
         }
     }
 
-    /**
-     * @return whether the shelf has any icons in it when a potential animation has finished, i.e
-     * if the current state would be applied right now
-     */
-    public boolean hasItemsInStableShelf() {
-        return mHasItemsInStableShelf;
-    }
-
-    public void setCollapsedIcons(NotificationIconContainer collapsedIcons) {
-        mCollapsedIcons = collapsedIcons;
-        mCollapsedIcons.addOnLayoutChangeListener(this);
-    }
-
     @Override
     public void onStateChanged(int newState) {
         mStatusBarState = newState;
@@ -983,20 +966,35 @@
     }
 
     @Override
-    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
-                               int oldTop, int oldRight, int oldBottom) {
-        updateRelativeOffset();
-    }
-
-    @Override
     public boolean needsClippingToShelf() {
         return false;
     }
 
+    private void assertRefactorFlagDisabled() {
+        if (mShelfRefactorFlagEnabled) {
+            throw new IllegalStateException(
+                    "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is enabled.");
+        }
+    }
+
+    private boolean checkRefactorFlagEnabled() {
+        if (!mShelfRefactorFlagEnabled) {
+            Log.wtf(TAG,
+                    "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is disabled.");
+        }
+        return mShelfRefactorFlagEnabled;
+    }
+
     public void setController(NotificationShelfController notificationShelfController) {
+        assertRefactorFlagDisabled();
         mController = notificationShelfController;
     }
 
+    public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) {
+        if (!checkRefactorFlagEnabled()) return;
+        mCanModifyColorOfNotifications = canModifyColorOfNotifications;
+    }
+
     public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) {
         mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
     }
@@ -1009,6 +1007,10 @@
         mSensitiveRevealAnimEndabled = enabled;
     }
 
+    public void setRefactorFlagEnabled(boolean enabled) {
+        mShelfRefactorFlagEnabled = enabled;
+    }
+
     /**
      * This method resets the OnScroll roundness of a view to 0f
      * <p>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
new file mode 100644
index 0000000..bf3d47c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+import android.view.View
+import android.view.View.OnClickListener
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView.OnActivatedListener
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.stack.AmbientState
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+
+/** Controller interface for [NotificationShelf]. */
+interface NotificationShelfController {
+    /** The [NotificationShelf] controlled by this Controller. */
+    val view: NotificationShelf
+
+    /** @see ExpandableView.getIntrinsicHeight */
+    val intrinsicHeight: Int
+
+    /** Container view for icons displayed in the shelf. */
+    val shelfIcons: NotificationIconContainer
+
+    /** Whether or not the shelf can modify the color of notifications in the shade. */
+    fun canModifyColorOfNotifications(): Boolean
+
+    /** @see ActivatableNotificationView.setOnActivatedListener */
+    fun setOnActivatedListener(listener: OnActivatedListener)
+
+    /** Binds the shelf to the host [NotificationStackScrollLayout], via its Controller. */
+    fun bind(
+        ambientState: AmbientState,
+        notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
+    )
+
+    /** @see View.setOnClickListener */
+    fun setOnClickListener(listener: OnClickListener)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 565c0a9..34300c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -38,8 +38,8 @@
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.carrier.QSCarrierGroupController;
 import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.shade.carrier.ShadeCarrierGroupController;
 import com.android.systemui.statusbar.ActionClickLogger;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.MediaArtworkProcessor;
@@ -78,14 +78,14 @@
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.time.SystemClock;
 
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
 import dagger.Binds;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
-import java.util.Optional;
-import java.util.concurrent.Executor;
-
 /**
  * This module provides instances needed to construct {@link CentralSurfacesImpl}. These are moved to
  * this separate from {@link CentralSurfacesModule} module so that components that wish to build
@@ -271,8 +271,8 @@
 
     /** */
     @Binds
-    QSCarrierGroupController.SlotIndexResolver provideSlotIndexResolver(
-            QSCarrierGroupController.SubscriptionManagerSlotIndexResolver impl);
+    ShadeCarrierGroupController.SlotIndexResolver provideSlotIndexResolver(
+            ShadeCarrierGroupController.SubscriptionManagerSlotIndexResolver impl);
 
     /**
      */
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 f7790e8..7898736 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
@@ -22,7 +22,6 @@
 import static android.app.Notification.CATEGORY_MESSAGE;
 import static android.app.Notification.CATEGORY_REMINDER;
 import static android.app.Notification.FLAG_BUBBLE;
-import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
@@ -825,8 +824,7 @@
             return false;
         }
 
-        if ((mSbn.getNotification().flags
-                & FLAG_FOREGROUND_SERVICE) != 0) {
+        if (mSbn.getNotification().isFgsOrUij()) {
             return true;
         }
         if (mSbn.getNotification().isMediaNotification()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
index 15ad312..1631ae2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.dagger.PeopleHeader
 import com.android.systemui.statusbar.notification.icon.ConversationIconManager
@@ -40,27 +41,28 @@
  */
 @CoordinatorScope
 class ConversationCoordinator @Inject constructor(
-    private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
-    private val conversationIconManager: ConversationIconManager,
-    @PeopleHeader peopleHeaderController: NodeController
+        private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
+        private val conversationIconManager: ConversationIconManager,
+        private val highPriorityProvider: HighPriorityProvider,
+        @PeopleHeader private val peopleHeaderController: NodeController,
 ) : Coordinator {
 
     private val promotedEntriesToSummaryOfSameChannel =
-        mutableMapOf<NotificationEntry, NotificationEntry>()
+            mutableMapOf<NotificationEntry, NotificationEntry>()
 
     private val onBeforeRenderListListener = OnBeforeRenderListListener { _ ->
         val unimportantSummaries = promotedEntriesToSummaryOfSameChannel
-            .mapNotNull { (promoted, summary) ->
-                val originalGroup = summary.parent
-                when {
-                    originalGroup == null -> null
-                    originalGroup == promoted.parent -> null
-                    originalGroup.parent == null -> null
-                    originalGroup.summary != summary -> null
-                    originalGroup.children.any { it.channel == summary.channel } -> null
-                    else -> summary.key
+                .mapNotNull { (promoted, summary) ->
+                    val originalGroup = summary.parent
+                    when {
+                        originalGroup == null -> null
+                        originalGroup == promoted.parent -> null
+                        originalGroup.parent == null -> null
+                        originalGroup.summary != summary -> null
+                        originalGroup.children.any { it.channel == summary.channel } -> null
+                        else -> summary.key
+                    }
                 }
-            }
         conversationIconManager.setUnimportantConversations(unimportantSummaries)
         promotedEntriesToSummaryOfSameChannel.clear()
     }
@@ -78,21 +80,23 @@
         }
     }
 
-    val sectioner = object : NotifSectioner("People", BUCKET_PEOPLE) {
+    val peopleAlertingSectioner = object : NotifSectioner("People(alerting)", BUCKET_PEOPLE) {
         override fun isInSection(entry: ListEntry): Boolean =
-                isConversation(entry)
+               highPriorityProvider.isHighPriorityConversation(entry)
 
-        override fun getComparator() = object : NotifComparator("People") {
-            override fun compare(entry1: ListEntry, entry2: ListEntry): Int {
-                val type1 = getPeopleType(entry1)
-                val type2 = getPeopleType(entry2)
-                return type2.compareTo(type1)
-            }
-        }
+        override fun getComparator(): NotifComparator = notifComparator
 
-        override fun getHeaderNodeController() =
-                // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController
-                if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null
+        override fun getHeaderNodeController(): NodeController? = conversationHeaderNodeController
+    }
+
+    val peopleSilentSectioner = object : NotifSectioner("People(silent)", BUCKET_PEOPLE) {
+        // Because the peopleAlertingSectioner is above this one, it will claim all conversations that are alerting.
+        // All remaining conversations must be silent.
+        override fun isInSection(entry: ListEntry): Boolean = isConversation(entry)
+
+        override fun getComparator(): NotifComparator = notifComparator
+
+        override fun getHeaderNodeController(): NodeController? = conversationHeaderNodeController
     }
 
     override fun attach(pipeline: NotifPipeline) {
@@ -101,15 +105,27 @@
     }
 
     private fun isConversation(entry: ListEntry): Boolean =
-        getPeopleType(entry) != TYPE_NON_PERSON
+            getPeopleType(entry) != TYPE_NON_PERSON
 
     @PeopleNotificationType
     private fun getPeopleType(entry: ListEntry): Int =
-        entry.representativeEntry?.let {
-            peopleNotificationIdentifier.getPeopleNotificationType(it)
-        } ?: TYPE_NON_PERSON
+            entry.representativeEntry?.let {
+                peopleNotificationIdentifier.getPeopleNotificationType(it)
+            } ?: TYPE_NON_PERSON
 
-    companion object {
+    private val notifComparator: NotifComparator = object : NotifComparator("People") {
+        override fun compare(entry1: ListEntry, entry2: ListEntry): Int {
+            val type1 = getPeopleType(entry1)
+            val type2 = getPeopleType(entry2)
+            return type2.compareTo(type1)
+        }
+    }
+
+    // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController
+    private val conversationHeaderNodeController: NodeController? =
+            if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null
+
+    private companion object {
         private const val TAG = "ConversationCoordinator"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 6bb5b92..02ce0d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.statusbar.notification.collection.PipelineDumper
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
 import javax.inject.Inject
 
 /**
@@ -32,6 +33,7 @@
 @CoordinatorScope
 class NotifCoordinatorsImpl @Inject constructor(
         notifPipelineFlags: NotifPipelineFlags,
+        sectionStyleProvider: SectionStyleProvider,
         dataStoreCoordinator: DataStoreCoordinator,
         hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
         hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
@@ -56,7 +58,7 @@
         viewConfigCoordinator: ViewConfigCoordinator,
         visualStabilityCoordinator: VisualStabilityCoordinator,
         sensitiveContentCoordinator: SensitiveContentCoordinator,
-        dismissibilityCoordinator: DismissibilityCoordinator
+        dismissibilityCoordinator: DismissibilityCoordinator,
 ) : NotifCoordinators {
 
     private val mCoordinators: MutableList<Coordinator> = ArrayList()
@@ -99,13 +101,20 @@
         mCoordinators.add(dismissibilityCoordinator)
 
         // Manually add Ordered Sections
-        // HeadsUp > FGS > People > Alerting > Silent > Minimized > Unknown/Default
-        mOrderedSections.add(headsUpCoordinator.sectioner)
+        mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
         mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
-        mOrderedSections.add(conversationCoordinator.sectioner) // People
+        mOrderedSections.add(conversationCoordinator.peopleAlertingSectioner) // People Alerting
+        mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent
         mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
         mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent
         mOrderedSections.add(rankingCoordinator.minimizedSectioner) // Minimized
+
+        sectionStyleProvider.setMinimizedSections(setOf(rankingCoordinator.minimizedSectioner))
+        sectionStyleProvider.setSilentSections(listOf(
+                conversationCoordinator.peopleSilentSectioner,
+                rankingCoordinator.silentSectioner,
+                rankingCoordinator.minimizedSectioner,
+        ))
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index ea5cb30..1d37dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -27,15 +27,12 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
 import com.android.systemui.statusbar.notification.collection.render.NodeController;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
 import com.android.systemui.statusbar.notification.dagger.AlertingHeader;
 import com.android.systemui.statusbar.notification.dagger.SilentHeader;
 import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
 
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 
 import javax.inject.Inject;
@@ -52,7 +49,6 @@
     public static final boolean SHOW_ALL_SECTIONS = false;
     private final StatusBarStateController mStatusBarStateController;
     private final HighPriorityProvider mHighPriorityProvider;
-    private final SectionStyleProvider mSectionStyleProvider;
     private final NodeController mSilentNodeController;
     private final SectionHeaderController mSilentHeaderController;
     private final NodeController mAlertingHeaderController;
@@ -63,13 +59,11 @@
     public RankingCoordinator(
             StatusBarStateController statusBarStateController,
             HighPriorityProvider highPriorityProvider,
-            SectionStyleProvider sectionStyleProvider,
             @AlertingHeader NodeController alertingHeaderController,
             @SilentHeader SectionHeaderController silentHeaderController,
             @SilentHeader NodeController silentNodeController) {
         mStatusBarStateController = statusBarStateController;
         mHighPriorityProvider = highPriorityProvider;
-        mSectionStyleProvider = sectionStyleProvider;
         mAlertingHeaderController = alertingHeaderController;
         mSilentNodeController = silentNodeController;
         mSilentHeaderController = silentHeaderController;
@@ -78,9 +72,6 @@
     @Override
     public void attach(NotifPipeline pipeline) {
         mStatusBarStateController.addCallback(mStatusBarStateCallback);
-        mSectionStyleProvider.setMinimizedSections(Collections.singleton(mMinimizedNotifSectioner));
-        mSectionStyleProvider.setSilentSections(
-                Arrays.asList(mSilentNotifSectioner, mMinimizedNotifSectioner));
 
         pipeline.addPreGroupFilter(mSuspendedFilter);
         pipeline.addPreGroupFilter(mDndVisualEffectsFilter);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
index e7ef2ec..731ec80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
@@ -16,10 +16,13 @@
 
 package com.android.systemui.statusbar.notification.collection.provider;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.NotificationManager;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -63,7 +66,7 @@
      * A GroupEntry is considered high priority if its representativeEntry (summary) or children are
      * high priority
      */
-    public boolean isHighPriority(ListEntry entry) {
+    public boolean isHighPriority(@Nullable ListEntry entry) {
         if (entry == null) {
             return false;
         }
@@ -78,6 +81,36 @@
                 || hasHighPriorityChild(entry);
     }
 
+    /**
+     * @return true if the ListEntry is high priority conversation, else false
+     */
+    public boolean isHighPriorityConversation(@NonNull ListEntry entry) {
+        final NotificationEntry notifEntry = entry.getRepresentativeEntry();
+        if (notifEntry == null) {
+            return  false;
+        }
+
+        if (!isPeopleNotification(notifEntry)) {
+            return false;
+        }
+
+        if (notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT) {
+            return true;
+        }
+
+        return isNotificationEntryWithAtLeastOneImportantChild(entry);
+    }
+
+    private boolean isNotificationEntryWithAtLeastOneImportantChild(@NonNull ListEntry entry) {
+        if (!(entry instanceof GroupEntry)) {
+            return false;
+        }
+        final GroupEntry groupEntry = (GroupEntry) entry;
+        return groupEntry.getChildren().stream().anyMatch(
+                childEntry ->
+                        childEntry.getRanking().getImportance()
+                                >= NotificationManager.IMPORTANCE_DEFAULT);
+    }
 
     private boolean hasHighPriorityChild(ListEntry entry) {
         if (entry instanceof NotificationEntry
@@ -93,7 +126,6 @@
                 }
             }
         }
-
         return false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index ca762fc..a48870b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -630,6 +630,11 @@
             return false;
         }
 
+        if (notification.isUserInitiatedJob()) {
+            if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "user initiated job");
+            return false;
+        }
+
         if (log) mLogger.logNoHeadsUpOldWhen(entry, when, age);
         final int uid = entry.getSbn().getUid();
         final String packageName = entry.getSbn().getPackageName();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
new file mode 100644
index 0000000..f2216fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.interruption
+
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.Decision
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision
+
+/**
+ * Wraps a [NotificationInterruptStateProvider] to convert it to the new
+ * [VisualInterruptionDecisionProvider] interface.
+ */
+@SysUISingleton
+class NotificationInterruptStateProviderWrapper(
+    private val wrapped: NotificationInterruptStateProvider
+) : VisualInterruptionDecisionProvider {
+
+    @VisibleForTesting
+    enum class DecisionImpl(override val shouldInterrupt: Boolean) : Decision {
+        SHOULD_INTERRUPT(shouldInterrupt = true),
+        SHOULD_NOT_INTERRUPT(shouldInterrupt = false);
+
+        companion object {
+            fun of(booleanDecision: Boolean) =
+                if (booleanDecision) SHOULD_INTERRUPT else SHOULD_NOT_INTERRUPT
+        }
+    }
+
+    @VisibleForTesting
+    class FullScreenIntentDecisionImpl(
+        val originalEntry: NotificationEntry,
+        val originalDecision: NotificationInterruptStateProvider.FullScreenIntentDecision
+    ) : FullScreenIntentDecision {
+        override val shouldInterrupt = originalDecision.shouldLaunch
+        override val wouldInterruptWithoutDnd = originalDecision == NO_FSI_SUPPRESSED_ONLY_BY_DND
+    }
+
+    override fun addSuppressor(suppressor: NotificationInterruptSuppressor) {
+        wrapped.addSuppressor(suppressor)
+    }
+
+    override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision =
+        wrapped.checkHeadsUp(entry, /* log= */ false).let { DecisionImpl.of(it) }
+
+    override fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision =
+        wrapped.checkHeadsUp(entry, /* log= */ true).let { DecisionImpl.of(it) }
+
+    override fun makeUnloggedFullScreenIntentDecision(entry: NotificationEntry) =
+        wrapped.getFullScreenIntentDecision(entry).let { FullScreenIntentDecisionImpl(entry, it) }
+
+    override fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) {
+        (decision as FullScreenIntentDecisionImpl).let {
+            wrapped.logFullScreenIntentDecision(it.originalEntry, it.originalDecision)
+        }
+    }
+
+    override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision =
+        wrapped.shouldBubbleUp(entry).let { DecisionImpl.of(it) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
new file mode 100644
index 0000000..c0f4fcd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.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.statusbar.notification.interruption
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+/**
+ * Decides whether a notification should visually interrupt the user in various ways.
+ *
+ * These include displaying the notification as heads-up (peeking while the device is awake or
+ * pulsing while the device is dozing), displaying the notification as a bubble, and launching a
+ * full-screen intent for the notification.
+ */
+interface VisualInterruptionDecisionProvider {
+    /**
+     * Represents the decision to visually interrupt or not.
+     *
+     * Used for heads-up and bubble decisions; subclassed by [FullScreenIntentDecision] for
+     * full-screen intent decisions.
+     *
+     * @property[shouldInterrupt] whether a visual interruption should be triggered
+     */
+    interface Decision {
+        val shouldInterrupt: Boolean
+    }
+
+    /**
+     * Represents the decision to launch a full-screen intent for a notification or not.
+     *
+     * @property[wouldInterruptWithoutDnd] whether a full-screen intent should not be launched only
+     *   because Do Not Disturb has suppressed it
+     */
+    interface FullScreenIntentDecision : Decision {
+        val wouldInterruptWithoutDnd: Boolean
+    }
+
+    /**
+     * Adds a [component][suppressor] that can suppress visual interruptions.
+     *
+     * This class may call suppressors in any order.
+     *
+     * @param[suppressor] the suppressor to add
+     */
+    fun addSuppressor(suppressor: NotificationInterruptSuppressor)
+
+    /**
+     * Decides whether a [notification][entry] should display as heads-up or not, but does not log
+     * that decision.
+     *
+     * @param[entry] the notification that this decision is about
+     * @return the decision to display that notification as heads-up or not
+     */
+    fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision
+
+    /**
+     * Decides whether a [notification][entry] should display as heads-up or not, and logs that
+     * decision.
+     *
+     * If the device is awake, the decision will consider whether the notification should "peek"
+     * (slide in from the top of the screen over the current activity).
+     *
+     * If the device is dozing, the decision will consider whether the notification should "pulse"
+     * (wake the screen up and display the ambient view of the notification).
+     *
+     * @see[makeUnloggedHeadsUpDecision]
+     *
+     * @param[entry] the notification that this decision is about
+     * @return the decision to display that notification as heads-up or not
+     */
+    fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision
+
+    /**
+     * Decides whether a [notification][entry] should launch a full-screen intent or not, but does
+     * not log that decision.
+     *
+     * The returned decision can be logged by passing it to [logFullScreenIntentDecision].
+     *
+     * @see[makeAndLogHeadsUpDecision]
+     *
+     * @param[entry] the notification that this decision is about
+     * @return the decision to launch a full-screen intent for that notification or not
+     */
+    fun makeUnloggedFullScreenIntentDecision(entry: NotificationEntry): FullScreenIntentDecision
+
+    /**
+     * Logs a previous [decision] to launch a full-screen intent or not.
+     *
+     * @param[decision] the decision to log
+     */
+    fun logFullScreenIntentDecision(decision: FullScreenIntentDecision)
+
+    /**
+     * Decides whether a [notification][entry] should display as a bubble or not.
+     *
+     * @param[entry] the notification that this decision is about
+     * @return the decision to display that notification as a bubble or not
+     */
+    fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java
index af8d6ec..98cd84d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java
@@ -16,8 +16,8 @@
 
 package com.android.systemui.statusbar.notification.row.dagger;
 
+import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl;
 import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 
 import dagger.Binds;
@@ -46,7 +46,8 @@
      * Creates a NotificationShelfController.
      */
     @NotificationRowScope
-    NotificationShelfController getNotificationShelfController();
+    LegacyNotificationShelfControllerImpl getNotificationShelfController();
+
     /**
      * Dagger Module that extracts interesting properties from a NotificationShelf.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
new file mode 100644
index 0000000..db550c0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shelf.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Interactor for the [NotificationShelf] */
+@CentralSurfacesComponent.CentralSurfacesScope
+class NotificationShelfInteractor
+@Inject
+constructor(
+    private val keyguardRepository: KeyguardRepository,
+    private val deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
+) {
+    /** Is the system in a state where the shelf is just a static display of notification icons? */
+    val isShelfStatic: Flow<Boolean>
+        get() =
+            combine(
+                keyguardRepository.isKeyguardShowing,
+                deviceEntryFaceAuthRepository.isBypassEnabled,
+            ) { isKeyguardShowing, isBypassEnabled ->
+                isKeyguardShowing && isBypassEnabled
+            }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
new file mode 100644
index 0000000..bd531ca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shelf.ui.viewbinder
+
+import android.view.View
+import android.view.View.OnAttachStateChangeListener
+import android.view.accessibility.AccessibilityManager
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController
+import com.android.systemui.statusbar.notification.row.ExpandableOutlineViewController
+import com.android.systemui.statusbar.notification.row.ExpandableViewController
+import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
+import com.android.systemui.statusbar.notification.stack.AmbientState
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.phone.NotificationTapHelper
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import javax.inject.Inject
+
+/**
+ * Controller class for [NotificationShelf]. This implementation serves as a temporary wrapper
+ * around a [NotificationShelfViewBinder], so that external code can continue to depend on the
+ * [NotificationShelfController] interface. Once the [LegacyNotificationShelfControllerImpl] is
+ * removed, this class can go away and the ViewBinder can be used directly.
+ */
+@CentralSurfacesScope
+class NotificationShelfViewBinderWrapperControllerImpl
+@Inject
+constructor(
+    private val shelf: NotificationShelf,
+    private val viewModel: NotificationShelfViewModel,
+    featureFlags: FeatureFlags,
+    private val notifTapHelperFactory: NotificationTapHelper.Factory,
+    private val a11yManager: AccessibilityManager,
+    private val falsingManager: FalsingManager,
+    private val falsingCollector: FalsingCollector,
+    private val statusBarStateController: SysuiStatusBarStateController,
+) : NotificationShelfController {
+
+    override val view: NotificationShelf
+        get() = shelf
+
+    init {
+        shelf.apply {
+            setRefactorFlagEnabled(featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR))
+            useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES))
+            setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM))
+        }
+    }
+
+    fun init() {
+        NotificationShelfViewBinder.bind(viewModel, shelf)
+
+        ActivatableNotificationViewController(
+                shelf,
+                notifTapHelperFactory,
+                ExpandableOutlineViewController(shelf, ExpandableViewController(shelf)),
+                a11yManager,
+                falsingManager,
+                falsingCollector,
+            )
+            .init()
+        val onAttachStateListener =
+            object : OnAttachStateChangeListener {
+                override fun onViewAttachedToWindow(v: View) {
+                    statusBarStateController.addCallback(
+                        shelf,
+                        SysuiStatusBarStateController.RANK_SHELF,
+                    )
+                }
+
+                override fun onViewDetachedFromWindow(v: View) {
+                    statusBarStateController.removeCallback(shelf)
+                }
+            }
+        shelf.addOnAttachStateChangeListener(onAttachStateListener)
+        if (shelf.isAttachedToWindow) {
+            onAttachStateListener.onViewAttachedToWindow(shelf)
+        }
+    }
+
+    override val intrinsicHeight: Int
+        get() = shelf.intrinsicHeight
+
+    override val shelfIcons: NotificationIconContainer
+        get() = shelf.shelfIcons
+
+    override fun canModifyColorOfNotifications(): Boolean = unsupported
+
+    override fun setOnActivatedListener(listener: ActivatableNotificationView.OnActivatedListener) {
+        shelf.setOnActivatedListener(listener)
+    }
+
+    override fun bind(
+        ambientState: AmbientState,
+        notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
+    ) {
+        shelf.bind(ambientState, notificationStackScrollLayoutController)
+    }
+
+    override fun setOnClickListener(listener: View.OnClickListener) {
+        shelf.setOnClickListener(listener)
+    }
+
+    private val unsupported: Nothing
+        get() = error("Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is enabled")
+}
+
+/** Binds a [NotificationShelf] to its backend. */
+object NotificationShelfViewBinder {
+    fun bind(viewModel: NotificationShelfViewModel, shelf: NotificationShelf) {
+        shelf.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                viewModel.canModifyColorOfNotifications
+                    .onEach(shelf::setCanModifyColorOfNotifications)
+                    .launchIn(this)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
new file mode 100644
index 0000000..b84834a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shelf.ui.viewmodel
+
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** ViewModel for [NotificationShelf]. */
+@CentralSurfacesScope
+class NotificationShelfViewModel
+@Inject
+constructor(
+    private val interactor: NotificationShelfInteractor,
+) {
+    /** Is the shelf allowed to modify the color of notifications in the host layout? */
+    val canModifyColorOfNotifications: Flow<Boolean>
+        get() = interactor.isShelfStatic.map { static -> !static }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index da2a32d..9f9ab5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -191,7 +191,9 @@
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.shade.ShadeLogger;
 import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shade.ShadeLogger;
 import com.android.systemui.statusbar.AutoHideUiElement;
 import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.CircleReveal;
@@ -506,6 +508,7 @@
     /** Controller for the Shade. */
     @VisibleForTesting
     NotificationPanelViewController mNotificationPanelViewController;
+    private final ShadeLogger mShadeLogger;
 
     // settings
     private QSPanelController mQSPanelController;
@@ -739,6 +742,7 @@
             KeyguardViewMediator keyguardViewMediator,
             DisplayMetrics displayMetrics,
             MetricsLogger metricsLogger,
+            ShadeLogger shadeLogger,
             @UiBackground Executor uiBgExecutor,
             NotificationMediaManager notificationMediaManager,
             NotificationLockscreenUserManager lockScreenUserManager,
@@ -831,6 +835,7 @@
         mKeyguardViewMediator = keyguardViewMediator;
         mDisplayMetrics = displayMetrics;
         mMetricsLogger = metricsLogger;
+        mShadeLogger = shadeLogger;
         mUiBgExecutor = uiBgExecutor;
         mMediaManager = notificationMediaManager;
         mLockscreenUserManager = lockScreenUserManager;
@@ -3675,6 +3680,10 @@
         boolean disabled = (!mDeviceInteractive && !mDozeServiceHost.isPulsing())
                 || goingToSleepWithoutAnimation
                 || mDeviceProvisionedController.isFrpActive();
+        mShadeLogger.logUpdateNotificationPanelTouchState(disabled, isGoingToSleep(),
+                !mDozeParameters.shouldControlScreenOff(), !mDeviceInteractive,
+                !mDozeServiceHost.isPulsing(), mDeviceProvisionedController.isFrpActive());
+
         mNotificationPanelViewController.setTouchAndAnimationDisabled(disabled);
         mNotificationIconAreaController.setAnimationsEnabled(!disabled);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index e4227dc..d433814 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder.bind
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
 
@@ -57,7 +58,7 @@
     }
 
     private var ambientIndicationArea: View? = null
-    private lateinit var binding: KeyguardBottomAreaViewBinder.Binding
+    private var binding: KeyguardBottomAreaViewBinder.Binding? = null
     private var lockIconViewController: LockIconViewController? = null
 
     /** Initializes the view. */
@@ -67,13 +68,16 @@
         lockIconViewController: LockIconViewController? = null,
         messageDisplayer: MessageDisplayer? = null,
         vibratorHelper: VibratorHelper? = null,
+        activityStarter: ActivityStarter? = null,
     ) {
+        binding?.destroy()
         binding =
             bind(
                 this,
                 viewModel,
                 falsingManager,
                 vibratorHelper,
+                activityStarter,
             ) {
                 messageDisplayer?.display(it)
             }
@@ -114,12 +118,12 @@
 
     override fun onConfigurationChanged(newConfig: Configuration) {
         super.onConfigurationChanged(newConfig)
-        binding.onConfigurationChanged()
+        binding?.onConfigurationChanged()
     }
 
     /** Returns a list of animators to use to animate the indication areas. */
     val indicationAreaAnimators: List<ViewPropertyAnimator>
-        get() = binding.getIndicationAreaAnimators()
+        get() = checkNotNull(binding).getIndicationAreaAnimators()
 
     override fun hasOverlappingRendering(): Boolean {
         return false
@@ -139,7 +143,7 @@
         super.onLayout(changed, left, top, right, bottom)
         findViewById<View>(R.id.ambient_indication_container)?.let {
             val (ambientLeft, ambientTop) = it.locationOnScreen
-            if (binding.shouldConstrainToTopOfLockIcon()) {
+            if (binding?.shouldConstrainToTopOfLockIcon() == true) {
                 // make top of ambient indication view the bottom of the lock icon
                 it.layout(
                     ambientLeft,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
deleted file mode 100644
index 076e5f1..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.WindowInsets;
-import android.widget.FrameLayout;
-
-/**
- * A view group which contains the preview of phone/camera and draws a black bar at the bottom as
- * the fake navigation bar.
- */
-public class KeyguardPreviewContainer extends FrameLayout {
-
-    private Drawable mBlackBarDrawable = new Drawable() {
-        @Override
-        public void draw(Canvas canvas) {
-            canvas.save();
-            canvas.clipRect(0, getHeight() - getPaddingBottom(), getWidth(), getHeight());
-            canvas.drawColor(Color.BLACK);
-            canvas.restore();
-        }
-
-        @Override
-        public void setAlpha(int alpha) {
-            // noop
-        }
-
-        @Override
-        public void setColorFilter(ColorFilter colorFilter) {
-            // noop
-        }
-
-        @Override
-        public int getOpacity() {
-            return android.graphics.PixelFormat.OPAQUE;
-        }
-    };
-
-    public KeyguardPreviewContainer(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        setBackground(mBlackBarDrawable);
-    }
-
-    @Override
-    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        setPadding(0, 0, 0, insets.getStableInsetBottom());
-        return super.onApplyWindowInsets(insets);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index eb19c0d..057fa42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -193,7 +193,6 @@
 
     public void setupShelf(NotificationShelfController notificationShelfController) {
         mShelfIcons = notificationShelfController.getShelfIcons();
-        notificationShelfController.setCollapsedIcons(mNotificationIcons);
     }
 
     public void onDensityOrFontScaleChanged(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
index b303151..c817466 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
@@ -85,17 +85,16 @@
 
     /**
      * Called when keyguard is about to be displayed and allows to perform custom animation
-     *
-     * @return A handle that can be used for cancelling the animation, if necessary
      */
-    fun animateInKeyguard(keyguardView: View, after: Runnable): AnimatorHandle? {
-        animations.forEach {
+    fun animateInKeyguard(keyguardView: View, after: Runnable) =
+        animations.firstOrNull {
             if (it.shouldAnimateInKeyguard()) {
-                return@animateInKeyguard it.animateInKeyguard(keyguardView, after)
+                it.animateInKeyguard(keyguardView, after)
+                true
+            } else {
+                false
             }
         }
-        return null
-    }
 
     /**
      * If returns true it will disable propagating touches to apps and keyguard
@@ -212,10 +211,7 @@
     fun onAlwaysOnChanged(alwaysOn: Boolean) {}
 
     fun shouldAnimateInKeyguard(): Boolean = false
-    fun animateInKeyguard(keyguardView: View, after: Runnable): AnimatorHandle? {
-        after.run()
-        return null
-    }
+    fun animateInKeyguard(keyguardView: View, after: Runnable) = after.run()
 
     fun shouldDelayKeyguardShow(): Boolean = false
     fun isKeyguardShowDelayed(): Boolean = false
@@ -228,7 +224,3 @@
     fun shouldAnimateDozingChange(): Boolean = true
     fun shouldAnimateClockChange(): Boolean = true
 }
-
-interface AnimatorHandle {
-    fun cancel()
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index edfc95f..c623201 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -618,9 +618,6 @@
         if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) {
             return false;
         }
-        if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
-            return false;
-        }
         return true;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index de7bf3c..d731f88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -230,7 +230,7 @@
         if (state == null) {
             return;
         }
-        if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) {
+        if (statusIcon.icon == R.drawable.ic_shade_no_calling_sms) {
             state.isNoCalling = statusIcon.visible;
             state.noCallingDescription = statusIcon.contentDescription;
         } else {
@@ -422,7 +422,7 @@
 
         private CallIndicatorIconState(int subId) {
             this.subId = subId;
-            this.noCallingResId = R.drawable.ic_qs_no_calling_sms;
+            this.noCallingResId = R.drawable.ic_shade_no_calling_sms;
             this.callStrengthResId = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0];
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
index 50cce45..6dc8065 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
@@ -16,6 +16,12 @@
 package com.android.systemui.statusbar.phone;
 
 public interface StatusBarWindowCallback {
-    void onStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean bouncerShowing,
-            boolean isDozing, boolean panelExpanded, boolean isDreaming);
+    /**
+     * Invoked when the internal state of NotificationShadeWindowControllerImpl changes.
+     * Some of the flags passed as argument to the callback might have changed, but this is not
+     * guaranteed.
+     */
+    void onStateChanged(boolean keyguardShowing, boolean keyguardOccluded,
+            boolean keyguardGoingAway, boolean bouncerShowing, boolean isDozing,
+            boolean panelExpanded, boolean isDreaming);
 }
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 deb0414..118bfc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -160,7 +160,7 @@
      * Animates in the provided keyguard view, ending in the same position that it will be in on
      * AOD.
      */
-    override fun animateInKeyguard(keyguardView: View, after: Runnable): AnimatorHandle {
+    override fun animateInKeyguard(keyguardView: View, after: Runnable) {
         shouldAnimateInKeyguard = false
         keyguardView.alpha = 0f
         keyguardView.visibility = View.VISIBLE
@@ -175,36 +175,11 @@
         // We animate the Y properly separately using the PropertyAnimator, as the panel
         // view also needs to update the end position.
         PropertyAnimator.cancelAnimation(keyguardView, AnimatableProperty.Y)
+        PropertyAnimator.setProperty<View>(keyguardView, AnimatableProperty.Y, currentY,
+                AnimationProperties().setDuration(duration.toLong()),
+                true /* animate */)
 
-        // Start the animation on the next frame using Choreographer APIs. animateInKeyguard() is
-        // called while the system is busy processing lots of requests, so delaying the animation a
-        // frame will mitigate jank. In the event the animation is cancelled before the next frame
-        // is called, this callback will be removed
-        val keyguardAnimator = keyguardView.animate()
-        val nextFrameCallback = TraceUtils.namedRunnable("startAnimateInKeyguard") {
-            PropertyAnimator.setProperty(keyguardView, AnimatableProperty.Y, currentY,
-                    AnimationProperties().setDuration(duration.toLong()),
-                    true /* animate */)
-            keyguardAnimator.start()
-        }
-        DejankUtils.postAfterTraversal(nextFrameCallback)
-        val animatorHandle = object : AnimatorHandle {
-            private var hasCancelled = false
-            override fun cancel() {
-                if (!hasCancelled) {
-                    DejankUtils.removeCallbacks(nextFrameCallback)
-                    // If we're cancelled, reset state flags/listeners. The end action above
-                    // will not be called, which is what we want since that will finish the
-                    // screen off animation and show the lockscreen, which we don't want if we
-                    // were cancelled.
-                    aodUiAnimationPlaying = false
-                    decidedToAnimateGoingToSleep = null
-                    keyguardView.animate().setListener(null)
-                    hasCancelled = true
-                }
-            }
-        }
-        keyguardAnimator
+        keyguardView.animate()
                 .setDuration(duration.toLong())
                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
                 .alpha(1f)
@@ -230,7 +205,14 @@
                 }
                 .setListener(object : AnimatorListenerAdapter() {
                     override fun onAnimationCancel(animation: Animator?) {
-                        animatorHandle.cancel()
+                        // If we're cancelled, reset state flags/listeners. The end action above
+                        // will not be called, which is what we want since that will finish the
+                        // screen off animation and show the lockscreen, which we don't want if we
+                        // were cancelled.
+                        aodUiAnimationPlaying = false
+                        decidedToAnimateGoingToSleep = null
+                        keyguardView.animate().setListener(null)
+
                         interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
                     }
 
@@ -240,7 +222,7 @@
                                 CUJ_SCREEN_OFF_SHOW_AOD)
                     }
                 })
-        return animatorHandle
+                .start()
     }
 
     override fun onStartedWakingUp() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 0929233..5d4adda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -33,6 +33,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.privacy.OngoingPrivacyChip;
 import com.android.systemui.settings.UserTracker;
@@ -44,12 +45,14 @@
 import com.android.systemui.shade.NotificationsQuickSettingsContainer;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
+import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
 import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
@@ -76,6 +79,7 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Named;
+import javax.inject.Provider;
 
 import dagger.Binds;
 import dagger.Module;
@@ -130,16 +134,24 @@
     @Provides
     @CentralSurfacesComponent.CentralSurfacesScope
     public static NotificationShelfController providesStatusBarWindowView(
+            FeatureFlags featureFlags,
+            Provider<NotificationShelfViewBinderWrapperControllerImpl> newImpl,
             NotificationShelfComponent.Builder notificationShelfComponentBuilder,
             NotificationShelf notificationShelf) {
-        NotificationShelfComponent component = notificationShelfComponentBuilder
-                .notificationShelf(notificationShelf)
-                .build();
-        NotificationShelfController notificationShelfController =
-                component.getNotificationShelfController();
-        notificationShelfController.init();
+        if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+            NotificationShelfViewBinderWrapperControllerImpl impl = newImpl.get();
+            impl.init();
+            return impl;
+        } else {
+            NotificationShelfComponent component = notificationShelfComponentBuilder
+                    .notificationShelf(notificationShelf)
+                    .build();
+            LegacyNotificationShelfControllerImpl notificationShelfController =
+                    component.getNotificationShelfController();
+            notificationShelfController.init();
 
-        return notificationShelfController;
+            return notificationShelfController;
+        }
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index fa71287..ea77163 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -60,6 +60,13 @@
      */
     val mobileIsDefault: StateFlow<Boolean>
 
+    /**
+     * True if the device currently has a carrier merged connection.
+     *
+     * See [CarrierMergedConnectionRepository] for more info.
+     */
+    val hasCarrierMergedConnection: Flow<Boolean>
+
     /** True if the default network connection is validated and false otherwise. */
     val defaultConnectionIsValidated: StateFlow<Boolean>
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index 44b5b3fa..eb20bba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -159,6 +159,15 @@
             .flatMapLatest { it.mobileIsDefault }
             .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.mobileIsDefault.value)
 
+    override val hasCarrierMergedConnection: StateFlow<Boolean> =
+        activeRepo
+            .flatMapLatest { it.hasCarrierMergedConnection }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                realRepository.hasCarrierMergedConnection.value,
+            )
+
     override val defaultConnectionIsValidated: StateFlow<Boolean> =
         activeRepo
             .flatMapLatest { it.defaultConnectionIsValidated }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 737bc68..0e4ceeb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -160,6 +160,9 @@
     override val mobileIsDefault: StateFlow<Boolean> = MutableStateFlow(true)
 
     // TODO(b/261029387): not yet supported
+    override val hasCarrierMergedConnection = MutableStateFlow(false)
+
+    // TODO(b/261029387): not yet supported
     override val defaultConnectionIsValidated: StateFlow<Boolean> = MutableStateFlow(true)
 
     override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 45d50c1..0e9b6c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -59,6 +59,7 @@
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
@@ -257,18 +258,32 @@
 
     override val mobileIsDefault: StateFlow<Boolean> =
         connectivityRepository.defaultConnections
-            // Because carrier merged networks are displayed as mobile networks, they're
-            // part of the `isDefault` calculation. See b/272586234.
-            .map { it.mobile.isDefault || it.carrierMerged.isDefault }
+            .map { it.mobile.isDefault }
             .distinctUntilChanged()
             .logDiffsForTable(
                 tableLogger,
-                columnPrefix = "",
+                columnPrefix = LOGGING_PREFIX,
                 columnName = "mobileIsDefault",
                 initialValue = false,
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
+    override val hasCarrierMergedConnection: StateFlow<Boolean> =
+        combine(
+                connectivityRepository.defaultConnections,
+                carrierMergedSubId,
+            ) { defaultConnections, carrierMergedSubId ->
+                defaultConnections.carrierMerged.isDefault || carrierMergedSubId != null
+            }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                tableLogger,
+                columnPrefix = LOGGING_PREFIX,
+                columnName = "hasCarrierMergedConnection",
+                initialValue = false,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
     override val defaultConnectionIsValidated: StateFlow<Boolean> =
         connectivityRepository.defaultConnections
             .map { it.isValidated }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 1e3122b..eec91a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -113,7 +113,22 @@
     private val context: Context,
 ) : MobileIconsInteractor {
 
-    override val mobileIsDefault = mobileConnectionsRepo.mobileIsDefault
+    override val mobileIsDefault =
+        combine(
+                mobileConnectionsRepo.mobileIsDefault,
+                mobileConnectionsRepo.hasCarrierMergedConnection,
+            ) { mobileIsDefault, hasCarrierMergedConnection ->
+                // Because carrier merged networks are displayed as mobile networks, they're part of
+                // the `isDefault` calculation. See b/272586234.
+                mobileIsDefault || hasCarrierMergedConnection
+            }
+            .logDiffsForTable(
+                tableLogger,
+                LOGGING_PREFIX,
+                columnName = "mobileIsDefault",
+                initialValue = false,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> =
         mobileConnectionsRepo.activeMobileDataRepository
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 dce7bf2..bfd133e 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
@@ -37,7 +37,6 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.stateIn
 
 /** Common interface for all of the location-based mobile icon view models. */
@@ -80,7 +79,12 @@
 ) : MobileIconViewModelCommon {
     /** Whether or not to show the error state of [SignalDrawable] */
     private val showExclamationMark: Flow<Boolean> =
-        iconInteractor.isDefaultDataEnabled.mapLatest { !it }
+        combine(
+            iconInteractor.isDefaultDataEnabled,
+            iconInteractor.isDefaultConnectionFailed,
+        ) { isDefaultDataEnabled, isDefaultConnectionFailed ->
+            !isDefaultDataEnabled || isDefaultConnectionFailed
+        }
 
     override val isVisible: StateFlow<Boolean> =
         if (!constants.hasDataCapabilities) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
index 6479f3d..731f1e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
@@ -44,11 +44,11 @@
 import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Ethernet
 import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Mobile
 import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Wifi
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
 import com.android.systemui.tuner.TunerService
 import java.io.PrintWriter
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -68,12 +68,12 @@
     val defaultConnections: StateFlow<DefaultConnectionModel>
 }
 
-@OptIn(ExperimentalCoroutinesApi::class)
+@SuppressLint("MissingPermission")
 @SysUISingleton
 class ConnectivityRepositoryImpl
 @Inject
 constructor(
-    connectivityManager: ConnectivityManager,
+    private val connectivityManager: ConnectivityManager,
     private val connectivitySlots: ConnectivitySlots,
     context: Context,
     dumpManager: DumpManager,
@@ -144,15 +144,14 @@
                         ) {
                             logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities)
 
+                            val wifiInfo =
+                                networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
                             val isWifiDefault =
-                                networkCapabilities.hasTransport(TRANSPORT_WIFI) ||
-                                    networkCapabilities.getMainOrUnderlyingWifiInfo() != null
+                                networkCapabilities.hasTransport(TRANSPORT_WIFI) || wifiInfo != null
                             val isMobileDefault =
                                 networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
-                            val isCarrierMergedDefault =
-                                networkCapabilities
-                                    .getMainOrUnderlyingWifiInfo()
-                                    ?.isCarrierMerged == true
+                            val isCarrierMergedDefault = wifiInfo?.isCarrierMerged == true
                             val isEthernetDefault =
                                 networkCapabilities.hasTransport(TRANSPORT_ETHERNET)
 
@@ -209,7 +208,32 @@
          * always use [WifiInfo] if it's available, so we need to check the underlying transport
          * info.
          */
-        fun NetworkCapabilities.getMainOrUnderlyingWifiInfo(): WifiInfo? {
+        fun NetworkCapabilities.getMainOrUnderlyingWifiInfo(
+            connectivityManager: ConnectivityManager,
+        ): WifiInfo? {
+            val mainWifiInfo = this.getMainWifiInfo()
+            if (mainWifiInfo != null) {
+                return mainWifiInfo
+            }
+            // Only CELLULAR networks may have underlying wifi information that's relevant to SysUI,
+            // so skip the underlying network check if it's not CELLULAR.
+            if (!this.hasTransport(TRANSPORT_CELLULAR)) {
+                return mainWifiInfo
+            }
+
+            // Some connections, like VPN connections, may have underlying networks that are
+            // eventually traced to a wifi or carrier merged connection. So, check those underlying
+            // networks for possible wifi information as well. See b/225902574.
+            return this.underlyingNetworks?.firstNotNullOfOrNull { underlyingNetwork ->
+                connectivityManager.getNetworkCapabilities(underlyingNetwork)?.getMainWifiInfo()
+            }
+        }
+
+        /**
+         * Checks the network capabilities for wifi info, but does *not* check the underlying
+         * networks. See [getMainOrUnderlyingWifiInfo].
+         */
+        private fun NetworkCapabilities.getMainWifiInfo(): WifiInfo? {
             // Wifi info can either come from a WIFI Transport, or from a CELLULAR transport for
             // virtual networks like VCN.
             val canHaveWifiInfo =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 08c14e7..f800cf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -33,6 +33,15 @@
 
     /** Observable for the current wifi network activity. */
     val wifiActivity: StateFlow<DataActivityModel>
+
+    /**
+     * Returns true if the device is currently connected to a wifi network with a valid SSID and
+     * false otherwise.
+     */
+    fun isWifiConnectedWithValidSsid(): Boolean {
+        val currentNetwork = wifiNetwork.value
+        return currentNetwork is WifiNetworkModel.Active && currentNetwork.hasValidSsid()
+    }
 }
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index f80aa68..b37c44a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -138,7 +138,8 @@
 
                             wifiNetworkChangeEvents.tryEmit(Unit)
 
-                            val wifiInfo = networkCapabilities.getMainOrUnderlyingWifiInfo()
+                            val wifiInfo =
+                                networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
                             if (wifiInfo?.isPrimary == true) {
                                 val wifiNetworkModel =
                                     createWifiNetworkModel(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index 96ab074..1a41abf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.domain.interactor
 
-import android.net.wifi.WifiManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -76,7 +75,7 @@
                     when {
                         info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint ->
                             info.passpointProviderFriendlyName
-                        info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid
+                        info.hasValidSsid() -> info.ssid
                         else -> null
                     }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
index 0923d78..4b33c88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.shared.model
 
+import android.net.wifi.WifiManager.UNKNOWN_SSID
 import android.telephony.SubscriptionManager
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.log.table.Diffable
@@ -223,6 +224,11 @@
             }
         }
 
+        /** Returns true if this network has a valid SSID and false otherwise. */
+        fun hasValidSsid(): Boolean {
+            return ssid != null && ssid != UNKNOWN_SSID
+        }
+
         override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
             if (prevVal !is Active) {
                 logFull(row)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 654ba04..1e63b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -21,6 +21,8 @@
 import static android.os.BatteryManager.EXTRA_HEALTH;
 import static android.os.BatteryManager.EXTRA_PRESENT;
 
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
+
 import android.annotation.WorkerThread;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -169,7 +171,8 @@
     @Override
     public void setPowerSaveMode(boolean powerSave, View view) {
         if (powerSave) mPowerSaverStartView.set(new WeakReference<>(view));
-        BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true);
+        BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true,
+                SAVER_ENABLED_QS);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index f1269f2..673819b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -38,14 +38,14 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Objects;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 /**
  */
 @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 08f7eae..a4b093d 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -241,8 +241,8 @@
 
         // Store callback in a field so it won't get GC'd
         mStatusBarWindowCallback =
-                (keyguardShowing, keyguardOccluded, bouncerShowing, isDozing, panelExpanded,
-                        isDreaming) ->
+                (keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, isDozing,
+                        panelExpanded, isDreaming) ->
                         mBubbles.onNotificationPanelExpandedChanged(panelExpanded);
         notificationShadeWindowController.registerCallback(mStatusBarWindowCallback);
 
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java
deleted file mode 100644
index e93e862..0000000
--- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java
+++ /dev/null
@@ -1,174 +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 androidx.core.animation;
-
-import android.os.Looper;
-import android.os.SystemClock;
-import android.util.AndroidRuntimeException;
-
-import androidx.annotation.NonNull;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * NOTE: this is a copy of the {@link androidx.core.animation.AnimatorTestRule} which attempts to
- * circumvent the problems with {@link androidx.core.animation.AnimationHandler} having a static
- * list of callbacks.
- *
- * TODO(b/275602127): remove this and use the original rule once we have the updated androidx code.
- */
-public final class AnimatorTestRule2 implements TestRule {
-
-    class TestAnimationHandler extends AnimationHandler {
-        TestAnimationHandler() {
-            super(new TestProvider());
-        }
-
-        List<AnimationFrameCallback> animationCallbacks = new ArrayList<>();
-
-        @Override
-        void addAnimationFrameCallback(AnimationFrameCallback callback) {
-            animationCallbacks.add(callback);
-            callback.doAnimationFrame(getCurrentTime());
-        }
-
-        @Override
-        public void removeCallback(AnimationFrameCallback callback) {
-            int id = animationCallbacks.indexOf(callback);
-            if (id >= 0) {
-                animationCallbacks.set(id, null);
-            }
-        }
-
-        void onAnimationFrame(long frameTime) {
-            for (int i = 0; i < animationCallbacks.size(); i++) {
-                final AnimationFrameCallback callback = animationCallbacks.get(i);
-                if (callback == null) {
-                    continue;
-                }
-                callback.doAnimationFrame(frameTime);
-            }
-        }
-
-        @Override
-        void autoCancelBasedOn(ObjectAnimator objectAnimator) {
-            for (int i = animationCallbacks.size() - 1; i >= 0; i--) {
-                AnimationFrameCallback cb = animationCallbacks.get(i);
-                if (cb == null) {
-                    continue;
-                }
-                if (objectAnimator.shouldAutoCancel(cb)) {
-                    ((Animator) animationCallbacks.get(i)).cancel();
-                }
-            }
-        }
-    }
-
-    final TestAnimationHandler mTestHandler;
-    final long mStartTime;
-    private long mTotalTimeDelta = 0;
-    private final Object mLock = new Object();
-
-    public AnimatorTestRule2() {
-        mStartTime = SystemClock.uptimeMillis();
-        mTestHandler = new TestAnimationHandler();
-    }
-
-    @NonNull
-    @Override
-    public Statement apply(@NonNull final Statement base, @NonNull Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                AnimationHandler.setTestHandler(mTestHandler);
-                try {
-                    base.evaluate();
-                } finally {
-                    AnimationHandler.setTestHandler(null);
-                }
-            }
-        };
-    }
-
-    /**
-     * Advances the animation clock by the given amount of delta in milliseconds. This call will
-     * produce an animation frame to all the ongoing animations. This method needs to be
-     * called on the same thread as {@link Animator#start()}.
-     *
-     * @param timeDelta the amount of milliseconds to advance
-     */
-    public void advanceTimeBy(long timeDelta) {
-        if (Looper.myLooper() == null) {
-            // Throw an exception
-            throw new AndroidRuntimeException("AnimationTestRule#advanceTimeBy(long) may only be"
-                    + "called on Looper threads");
-        }
-        synchronized (mLock) {
-            // Advance time & pulse a frame
-            mTotalTimeDelta += timeDelta < 0 ? 0 : timeDelta;
-        }
-        // produce a frame
-        mTestHandler.onAnimationFrame(getCurrentTime());
-    }
-
-
-    /**
-     * Returns the current time in milliseconds tracked by AnimationHandler. Note that this is a
-     * different time than the time tracked by {@link SystemClock} This method needs to be called on
-     * the same thread as {@link Animator#start()}.
-     */
-    public long getCurrentTime() {
-        if (Looper.myLooper() == null) {
-            // Throw an exception
-            throw new AndroidRuntimeException("AnimationTestRule#getCurrentTime() may only be"
-                    + "called on Looper threads");
-        }
-        synchronized (mLock) {
-            return mStartTime + mTotalTimeDelta;
-        }
-    }
-
-
-    private class TestProvider implements AnimationHandler.AnimationFrameCallbackProvider {
-        TestProvider() {
-        }
-
-        @Override
-        public void onNewCallbackAdded(AnimationHandler.AnimationFrameCallback callback) {
-            callback.doAnimationFrame(getCurrentTime());
-        }
-
-        @Override
-        public void postFrameCallback() {
-        }
-
-        @Override
-        public void setFrameDelay(long delay) {
-        }
-
-        @Override
-        public long getFrameDelay() {
-            return 0;
-        }
-    }
-}
-
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
index bddd60b..e7738af 100644
--- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
+++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
@@ -30,7 +30,7 @@
 @RunWithLooper(setAsMainLooper = true)
 class AnimatorTestRuleTest : SysuiTestCase() {
 
-    @get:Rule val animatorTestRule = AnimatorTestRule2()
+    @get:Rule val animatorTestRule = AnimatorTestRule()
 
     @Test
     fun testA() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index ecf7e0d..5557efa 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -38,8 +38,6 @@
 import static org.mockito.Mockito.when;
 
 import android.content.pm.PackageManager;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
 import android.provider.Settings;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
@@ -52,6 +50,8 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository;
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -88,8 +88,7 @@
     private static final SubscriptionInfo TEST_SUBSCRIPTION_ROAMING = new SubscriptionInfo(0, "", 0,
             TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "",
             DATA_ROAMING_ENABLE, null, null, null, null, false, null, "");
-    @Mock
-    private WifiManager mWifiManager;
+    private FakeWifiRepository mWifiRepository = new FakeWifiRepository();
     @Mock
     private WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock
@@ -121,7 +120,6 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        mContext.addMockSystemService(WifiManager.class, mWifiManager);
         mContext.addMockSystemService(PackageManager.class, mPackageManager);
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
         mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
@@ -144,7 +142,7 @@
         when(mTelephonyManager.getActiveModemCount()).thenReturn(3);
 
         mCarrierTextManager = new CarrierTextManager.Builder(
-                mContext, mContext.getResources(), mWifiManager,
+                mContext, mContext.getResources(), mWifiRepository,
                 mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, mMainExecutor,
                 mBgExecutor, mKeyguardUpdateMonitor)
                 .setShowAirplaneMode(true)
@@ -364,7 +362,11 @@
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
                 TelephonyManager.SIM_STATE_READY);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
-        mockWifi();
+
+        assertFalse(mWifiRepository.isWifiConnectedWithValidSsid());
+        mWifiRepository.setWifiNetwork(
+                new WifiNetworkModel.Active(0, false, 0, "", false, false, null));
+        assertTrue(mWifiRepository.isWifiConnectedWithValidSsid());
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
         ServiceState ss = mock(ServiceState.class);
@@ -385,13 +387,6 @@
         assertNotEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText);
     }
 
-    private void mockWifi() {
-        when(mWifiManager.isWifiEnabled()).thenReturn(true);
-        WifiInfo wifiInfo = mock(WifiInfo.class);
-        when(wifiInfo.getBSSID()).thenReturn("");
-        when(mWifiManager.getConnectionInfo()).thenReturn(wifiInfo);
-    }
-
     @Test
     public void testCreateInfo_noSubscriptions() {
         reset(mCarrierTextCallback);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 2f627cb..b9f8dd94 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -48,8 +48,10 @@
 import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.plugins.ClockEvents;
+import com.android.systemui.plugins.ClockFaceConfig;
 import com.android.systemui.plugins.ClockFaceController;
 import com.android.systemui.plugins.ClockFaceEvents;
+import com.android.systemui.plugins.ClockTickRate;
 import com.android.systemui.plugins.log.LogBuffer;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.clocks.AnimatableClockView;
@@ -185,6 +187,10 @@
         when(mClockController.getAnimations()).thenReturn(mClockAnimations);
         when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
         when(mClockEventController.getClock()).thenReturn(mClockController);
+        when(mSmallClockController.getConfig())
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false));
+        when(mLargeClockController.getConfig())
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false));
 
         mSliceView = new View(getContext());
         when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
@@ -367,6 +373,28 @@
     }
 
     @Test
+    public void testChangeClockDateWeatherEnabled_SetsDateWeatherViewVisibility() {
+        ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor =
+                ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class);
+        when(mSmartspaceController.isEnabled()).thenReturn(true);
+        when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true);
+        when(mSmartspaceController.isWeatherEnabled()).thenReturn(true);
+        mController.init();
+        mExecutor.runAllReady();
+        assertEquals(View.VISIBLE, mFakeDateView.getVisibility());
+
+        when(mSmallClockController.getConfig())
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true));
+        when(mLargeClockController.getConfig())
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true));
+        verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture());
+        listenerArgumentCaptor.getValue().onCurrentClockChanged();
+
+        mExecutor.runAllReady();
+        assertEquals(View.GONE, mFakeDateView.getVisibility());
+    }
+
+    @Test
     public void testGetClock_nullClock_returnsNull() {
         when(mClockEventController.getClock()).thenReturn(null);
         assertNull(mController.getClock());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 48f7d92..f1ee108 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -16,17 +16,15 @@
 
 package com.android.keyguard;
 
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 
+import com.android.internal.jank.InteractionJankMonitor;
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.ClockController;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -45,26 +43,21 @@
 @RunWith(AndroidTestingRunner.class)
 public class KeyguardStatusViewControllerTest extends SysuiTestCase {
 
-    @Mock
-    private KeyguardStatusView mKeyguardStatusView;
-    @Mock
-    private KeyguardSliceViewController mKeyguardSliceViewController;
-    @Mock
-    private KeyguardClockSwitchController mKeyguardClockSwitchController;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
-    @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
-    ConfigurationController mConfigurationController;
-    @Mock
-    DozeParameters mDozeParameters;
-    @Mock
-    ScreenOffAnimationController mScreenOffAnimationController;
+    @Mock private KeyguardStatusView mKeyguardStatusView;
+    @Mock private KeyguardSliceViewController mKeyguardSliceViewController;
+    @Mock private KeyguardClockSwitchController mKeyguardClockSwitchController;
+    @Mock private KeyguardStateController mKeyguardStateController;
+    @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock private ConfigurationController mConfigurationController;
+    @Mock private DozeParameters mDozeParameters;
+    @Mock private ScreenOffAnimationController mScreenOffAnimationController;
+    @Mock private KeyguardLogger mKeyguardLogger;
+    @Mock private KeyguardStatusViewController mControllerMock;
+    @Mock private FeatureFlags mFeatureFlags;
+    @Mock private InteractionJankMonitor mInteractionJankMonitor;
+
     @Captor
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
-    @Mock
-    KeyguardLogger mKeyguardLogger;
 
     private KeyguardStatusViewController mController;
 
@@ -81,7 +74,9 @@
                 mConfigurationController,
                 mDozeParameters,
                 mScreenOffAnimationController,
-                mKeyguardLogger);
+                mKeyguardLogger,
+                mFeatureFlags,
+                mInteractionJankMonitor);
     }
 
     @Test
@@ -116,12 +111,4 @@
         configurationListenerArgumentCaptor.getValue().onLocaleListChanged();
         verify(mKeyguardClockSwitchController).onLocaleListChanged();
     }
-
-    @Test
-    public void getClock_forwardsToClockSwitch() {
-        ClockController mockClock = mock(ClockController.class);
-        when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
-
-        assertEquals(mockClock, mController.getClockController());
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 08813a7..3eb9590 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -20,9 +20,12 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_CONVENIENCE;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
 import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN;
 import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_STARTED_WAKING_UP;
 import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
 import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
 import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
 
@@ -41,6 +44,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.Assert.assertEquals;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -79,7 +84,6 @@
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
-import android.hardware.biometrics.SensorProperties;
 import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorProperties;
@@ -283,33 +287,13 @@
     @Before
     public void setup() throws RemoteException {
         MockitoAnnotations.initMocks(this);
-
-        mFaceSensorProperties =
-                List.of(createFaceSensorProperties(/* supportsFaceDetection = */ false));
-        when(mFaceManager.isHardwareDetected()).thenReturn(true);
-        when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true);
-        when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties);
         when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId);
 
-        mFingerprintSensorProperties = List.of(
-                new FingerprintSensorPropertiesInternal(1 /* sensorId */,
-                        FingerprintSensorProperties.STRENGTH_STRONG,
-                        1 /* maxEnrollmentsPerUser */,
-                        List.of(new ComponentInfoInternal("fingerprintSensor" /* componentId */,
-                                "vendor/model/revision" /* hardwareVersion */,
-                                "1.01" /* firmwareVersion */,
-                                "00000001" /* serialNumber */, "" /* softwareVersion */)),
-                        FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
-                        false /* resetLockoutRequiresHAT */));
-        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
-        when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
-        when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(
-                mFingerprintSensorProperties);
         when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
         when(mUserManager.isPrimaryUser()).thenReturn(true);
         when(mStrongAuthTracker.getStub()).thenReturn(mock(IStrongAuthTracker.Stub.class));
         when(mStrongAuthTracker
-                .isUnlockingWithBiometricAllowed(anyBoolean() /* isStrongBiometric */))
+                .isUnlockingWithBiometricAllowed(anyBoolean() /* isClass3Biometric */))
                 .thenReturn(true);
         when(mTelephonyManager.getServiceStateForSubscriber(anyInt()))
                 .thenReturn(new ServiceState());
@@ -346,20 +330,9 @@
                         anyInt());
 
         mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
-
-        ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> faceCaptor =
-                ArgumentCaptor.forClass(IFaceAuthenticatorsRegisteredCallback.class);
-        verify(mFaceManager).addAuthenticatorsRegisteredCallback(faceCaptor.capture());
-        mFaceAuthenticatorsRegisteredCallback = faceCaptor.getValue();
-        mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
-
-        ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> fingerprintCaptor =
-                ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class);
-        verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
-                fingerprintCaptor.capture());
-        mFingerprintAuthenticatorsRegisteredCallback = fingerprintCaptor.getValue();
-        mFingerprintAuthenticatorsRegisteredCallback
-                .onAllAuthenticatorsRegistered(mFingerprintSensorProperties);
+        captureAuthenticatorsRegisteredCallbacks();
+        setupFaceAuth(/* isClass3 */ false);
+        setupFingerprintAuth(/* isClass3 */ true);
 
         verify(mBiometricManager)
                 .registerEnabledOnKeyguardCallback(mBiometricEnabledCallbackArgCaptor.capture());
@@ -381,8 +354,64 @@
         when(mAuthController.areAllFingerprintAuthenticatorsRegistered()).thenReturn(true);
     }
 
+    private void captureAuthenticatorsRegisteredCallbacks() throws RemoteException {
+        ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> faceCaptor =
+                ArgumentCaptor.forClass(IFaceAuthenticatorsRegisteredCallback.class);
+        verify(mFaceManager).addAuthenticatorsRegisteredCallback(faceCaptor.capture());
+        mFaceAuthenticatorsRegisteredCallback = faceCaptor.getValue();
+        mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
+
+        ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> fingerprintCaptor =
+                ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class);
+        verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
+                fingerprintCaptor.capture());
+        mFingerprintAuthenticatorsRegisteredCallback = fingerprintCaptor.getValue();
+        mFingerprintAuthenticatorsRegisteredCallback
+                .onAllAuthenticatorsRegistered(mFingerprintSensorProperties);
+    }
+
+    private void setupFaceAuth(boolean isClass3) throws RemoteException {
+        when(mFaceManager.isHardwareDetected()).thenReturn(true);
+        when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true);
+        mFaceSensorProperties =
+                List.of(createFaceSensorProperties(/* supportsFaceDetection = */ false, isClass3));
+        when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties);
+        mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
+        assertEquals(isClass3, mKeyguardUpdateMonitor.isFaceClass3());
+    }
+
+    private void setupFingerprintAuth(boolean isClass3) throws RemoteException {
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+        mFingerprintSensorProperties = List.of(
+                createFingerprintSensorPropertiesInternal(TYPE_UDFPS_OPTICAL, isClass3));
+        when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(
+                mFingerprintSensorProperties);
+        mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
+                mFingerprintSensorProperties);
+        assertEquals(isClass3, mKeyguardUpdateMonitor.isFingerprintClass3());
+    }
+
+    private FingerprintSensorPropertiesInternal createFingerprintSensorPropertiesInternal(
+            @FingerprintSensorProperties.SensorType int sensorType,
+            boolean isClass3) {
+        final List<ComponentInfoInternal> componentInfo =
+                List.of(new ComponentInfoInternal("fingerprintSensor" /* componentId */,
+                        "vendor/model/revision" /* hardwareVersion */,
+                        "1.01" /* firmwareVersion */,
+                        "00000001" /* serialNumber */, "" /* softwareVersion */));
+        return new FingerprintSensorPropertiesInternal(
+                FINGERPRINT_SENSOR_ID,
+                isClass3 ? STRENGTH_STRONG : STRENGTH_CONVENIENCE,
+                1 /* maxEnrollmentsPerUser */,
+                componentInfo,
+                sensorType,
+                true /* resetLockoutRequiresHardwareAuthToken */);
+    }
+
     @NonNull
-    private FaceSensorPropertiesInternal createFaceSensorProperties(boolean supportsFaceDetection) {
+    private FaceSensorPropertiesInternal createFaceSensorProperties(
+            boolean supportsFaceDetection, boolean isClass3) {
         final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
         componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
                 "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
@@ -391,10 +420,9 @@
                 "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
                 "vendor/version/revision" /* softwareVersion */));
 
-
         return new FaceSensorPropertiesInternal(
-                0 /* id */,
-                FaceSensorProperties.STRENGTH_STRONG,
+                FACE_SENSOR_ID /* id */,
+                isClass3 ? STRENGTH_STRONG : STRENGTH_CONVENIENCE,
                 1 /* maxTemplatesAllowed */,
                 componentInfo,
                 FaceSensorProperties.TYPE_UNKNOWN,
@@ -686,7 +714,7 @@
     @Test
     public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricAllowed() {
         // GIVEN unlocking with biometric is allowed
-        strongAuthNotRequired();
+        primaryAuthNotRequiredByStrongAuthTracker();
 
         // THEN unlocking with face and fp is allowed
         Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
@@ -706,12 +734,15 @@
     }
 
     @Test
-    public void testUnlockingWithFaceAllowed_fingerprintLockout() {
-        // GIVEN unlocking with biometric is allowed
-        strongAuthNotRequired();
+    public void class3FingerprintLockOut_lockOutClass1Face() throws RemoteException {
+        setupFaceAuth(/* isClass3 */ false);
+        setupFingerprintAuth(/* isClass3 */ true);
 
-        // WHEN fingerprint is locked out
-        fingerprintErrorTemporaryLockedOut();
+        // GIVEN primary auth is not required by StrongAuthTracker
+        primaryAuthNotRequiredByStrongAuthTracker();
+
+        // WHEN fingerprint (class 3) is lock out
+        fingerprintErrorTemporaryLockOut();
 
         // THEN unlocking with face is not allowed
         Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
@@ -719,6 +750,54 @@
     }
 
     @Test
+    public void class3FingerprintLockOut_lockOutClass3Face() throws RemoteException {
+        setupFaceAuth(/* isClass3 */ true);
+        setupFingerprintAuth(/* isClass3 */ true);
+
+        // GIVEN primary auth is not required by StrongAuthTracker
+        primaryAuthNotRequiredByStrongAuthTracker();
+
+        // WHEN fingerprint (class 3) is lock out
+        fingerprintErrorTemporaryLockOut();
+
+        // THEN unlocking with face is not allowed
+        Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                BiometricSourceType.FACE));
+    }
+
+    @Test
+    public void class3FaceLockOut_lockOutClass3Fingerprint() throws RemoteException {
+        setupFaceAuth(/* isClass3 */ true);
+        setupFingerprintAuth(/* isClass3 */ true);
+
+        // GIVEN primary auth is not required by StrongAuthTracker
+        primaryAuthNotRequiredByStrongAuthTracker();
+
+        // WHEN face (class 3) is lock out
+        faceAuthLockOut();
+
+        // THEN unlocking with fingerprint is not allowed
+        Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                BiometricSourceType.FINGERPRINT));
+    }
+
+    @Test
+    public void class1FaceLockOut_doesNotLockOutClass3Fingerprint() throws RemoteException {
+        setupFaceAuth(/* isClass3 */ false);
+        setupFingerprintAuth(/* isClass3 */ true);
+
+        // GIVEN primary auth is not required by StrongAuthTracker
+        primaryAuthNotRequiredByStrongAuthTracker();
+
+        // WHEN face (class 1) is lock out
+        faceAuthLockOut();
+
+        // THEN unlocking with fingerprint is still allowed
+        Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                BiometricSourceType.FINGERPRINT));
+    }
+
+    @Test
     public void testUnlockingWithFpAllowed_strongAuthTrackerUnlockingWithBiometricNotAllowed() {
         // GIVEN unlocking with biometric is not allowed
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
@@ -731,10 +810,10 @@
     @Test
     public void testUnlockingWithFpAllowed_fingerprintLockout() {
         // GIVEN unlocking with biometric is allowed
-        strongAuthNotRequired();
+        primaryAuthNotRequiredByStrongAuthTracker();
 
-        // WHEN fingerprint is locked out
-        fingerprintErrorTemporaryLockedOut();
+        // WHEN fingerprint is lock out
+        fingerprintErrorTemporaryLockOut();
 
         // THEN unlocking with fingerprint is not allowed
         Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
@@ -757,8 +836,8 @@
         mKeyguardUpdateMonitor.onTrustChanged(true, true, getCurrentUser(), 0, null);
         Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
 
-        // WHEN fingerprint is locked out
-        fingerprintErrorTemporaryLockedOut();
+        // WHEN fingerprint is lock out
+        fingerprintErrorTemporaryLockOut();
 
         // THEN user is NOT considered as "having trust" and bouncer cannot be skipped
         Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
@@ -826,7 +905,7 @@
 
         // GIVEN udfps is supported and strong auth required for weak biometrics (face) only
         givenUdfpsSupported();
-        strongAuthRequiredForWeakBiometricOnly(); // this allows fingerprint to run but not face
+        primaryAuthRequiredForWeakBiometricOnly(); // allows class3 fp to run but not class1 face
 
         // WHEN the device wakes up
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
@@ -863,7 +942,7 @@
     public void noFaceDetect_whenStrongAuthRequiredAndBypass_faceDetectionUnsupported() {
         // GIVEN bypass is enabled, face detection is NOT supported and strong auth is required
         lockscreenBypassIsAllowed();
-        strongAuthRequiredEncrypted();
+        primaryAuthRequiredEncrypted();
         keyguardIsVisible();
 
         // WHEN the device wakes up
@@ -931,6 +1010,7 @@
         assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse();
         verifyFingerprintAuthenticateNeverCalled();
         // WHEN alternate bouncer is shown
+        mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
         mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
 
         // THEN make sure FP listening begins
@@ -1011,10 +1091,10 @@
 
     @Test
     public void testOnFaceAuthenticated_skipsFaceWhenAuthenticated() {
-        // test whether face will be skipped if authenticated, so the value of isStrongBiometric
+        // test whether face will be skipped if authenticated, so the value of isClass3Biometric
         // doesn't matter here
         mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser(),
-                true /* isStrongBiometric */);
+                true /* isClass3Biometric */);
         setKeyguardBouncerVisibility(true);
         mTestableLooper.processAllMessages();
 
@@ -1027,7 +1107,7 @@
         mTestableLooper.processAllMessages();
         keyguardIsVisible();
 
-        faceAuthLockedOut();
+        faceAuthLockOut();
 
         verify(mLockPatternUtils, never()).requireStrongAuth(anyInt(), anyInt());
     }
@@ -1050,7 +1130,7 @@
         mTestableLooper.processAllMessages();
         keyguardIsVisible();
 
-        faceAuthLockedOut();
+        faceAuthLockOut();
         mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
                 .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "");
 
@@ -1060,32 +1140,32 @@
     @Test
     public void testGetUserCanSkipBouncer_whenFace() {
         int user = KeyguardUpdateMonitor.getCurrentUser();
-        mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isStrongBiometric */);
+        mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isClass3Biometric */);
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
     }
 
     @Test
     public void testGetUserCanSkipBouncer_whenFace_nonStrongAndDisallowed() {
-        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */))
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */))
                 .thenReturn(false);
         int user = KeyguardUpdateMonitor.getCurrentUser();
-        mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isStrongBiometric */);
+        mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isClass3Biometric */);
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
     }
 
     @Test
     public void testGetUserCanSkipBouncer_whenFingerprint() {
         int user = KeyguardUpdateMonitor.getCurrentUser();
-        mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, true /* isStrongBiometric */);
+        mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, true /* isClass3Biometric */);
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
     }
 
     @Test
     public void testGetUserCanSkipBouncer_whenFingerprint_nonStrongAndDisallowed() {
-        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */))
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */))
                 .thenReturn(false);
         int user = KeyguardUpdateMonitor.getCurrentUser();
-        mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, false /* isStrongBiometric */);
+        mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, false /* isClass3Biometric */);
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
     }
 
@@ -1126,9 +1206,9 @@
             @BiometricConstants.LockoutMode int fingerprintLockoutMode,
             @BiometricConstants.LockoutMode int faceLockoutMode) {
         final int newUser = 12;
-        final boolean faceLocked =
+        final boolean faceLockOut =
                 faceLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
-        final boolean fpLocked =
+        final boolean fpLockOut =
                 fingerprintLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
 
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
@@ -1161,8 +1241,8 @@
                 eq(false), eq(BiometricSourceType.FINGERPRINT));
 
         // THEN locked out states are updated
-        assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLocked);
-        assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLocked);
+        assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLockOut);
+        assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLockOut);
 
         // Fingerprint should be cancelled on lockout if going to lockout state, else
         // restarted if it's not
@@ -1443,7 +1523,8 @@
             throws RemoteException {
         // GIVEN SFPS supported and enrolled
         final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
-        props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON));
+        props.add(createFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON,
+                /* isClass3 */ true));
         when(mAuthController.getSfpsProps()).thenReturn(props);
         when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
 
@@ -1466,17 +1547,6 @@
         assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
     }
 
-    private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal(
-            @FingerprintSensorProperties.SensorType int sensorType) {
-        return new FingerprintSensorPropertiesInternal(
-                0 /* sensorId */,
-                SensorProperties.STRENGTH_STRONG,
-                1 /* maxEnrollmentsPerUser */,
-                new ArrayList<ComponentInfoInternal>(),
-                sensorType,
-                true /* resetLockoutRequiresHardwareAuthToken */);
-    }
-
     @Test
     public void testShouldNotListenForUdfps_whenTrustEnabled() {
         // GIVEN a "we should listen for udfps" state
@@ -1613,7 +1683,7 @@
         keyguardNotGoingAway();
         occludingAppRequestsFaceAuth();
         currentUserIsPrimary();
-        strongAuthNotRequired();
+        primaryAuthNotRequiredByStrongAuthTracker();
         biometricsEnabledForCurrentUser();
         currentUserDoesNotHaveTrust();
         biometricsNotDisabledThroughDevicePolicyManager();
@@ -1622,7 +1692,7 @@
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
 
         // Fingerprint is locked out.
-        fingerprintErrorTemporaryLockedOut();
+        fingerprintErrorTemporaryLockOut();
 
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
     }
@@ -1634,7 +1704,7 @@
         bouncerFullyVisibleAndNotGoingToSleep();
         keyguardNotGoingAway();
         currentUserIsPrimary();
-        strongAuthNotRequired();
+        primaryAuthNotRequiredByStrongAuthTracker();
         biometricsEnabledForCurrentUser();
         currentUserDoesNotHaveTrust();
         biometricsNotDisabledThroughDevicePolicyManager();
@@ -1657,7 +1727,7 @@
         bouncerFullyVisibleAndNotGoingToSleep();
         keyguardNotGoingAway();
         currentUserIsPrimary();
-        strongAuthNotRequired();
+        primaryAuthNotRequiredByStrongAuthTracker();
         biometricsEnabledForCurrentUser();
         currentUserDoesNotHaveTrust();
         biometricsNotDisabledThroughDevicePolicyManager();
@@ -1684,7 +1754,7 @@
         // Face auth should run when the following is true.
         keyguardNotGoingAway();
         bouncerFullyVisibleAndNotGoingToSleep();
-        strongAuthNotRequired();
+        primaryAuthNotRequiredByStrongAuthTracker();
         biometricsEnabledForCurrentUser();
         currentUserDoesNotHaveTrust();
         biometricsNotDisabledThroughDevicePolicyManager();
@@ -1940,7 +2010,7 @@
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
 
         // Face is locked out.
-        faceAuthLockedOut();
+        faceAuthLockOut();
         mTestableLooper.processAllMessages();
 
         // This is needed beccause we want to show face locked out error message whenever face auth
@@ -2578,7 +2648,7 @@
         verify(mFingerprintManager).addLockoutResetCallback(fpLockoutResetCallbackCaptor.capture());
 
         // GIVEN device is locked out
-        fingerprintErrorTemporaryLockedOut();
+        fingerprintErrorTemporaryLockOut();
 
         // GIVEN FP detection is running
         givenDetectFingerprintWithClearingFingerprintManagerInvocations();
@@ -2662,6 +2732,21 @@
                 KeyguardUpdateMonitor.getCurrentUser())).isTrue();
     }
 
+    @Test
+    public void testFingerprintListeningStateWhenOccluded() {
+        when(mAuthController.isUdfpsSupported()).thenReturn(true);
+
+        mKeyguardUpdateMonitor.setKeyguardShowing(false, false);
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_BIOMETRIC);
+        mKeyguardUpdateMonitor.setKeyguardShowing(false, true);
+
+        verifyFingerprintAuthenticateNeverCalled();
+
+        mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
+        mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
+
+        verifyFingerprintAuthenticateCall();
+    }
 
     private void verifyFingerprintAuthenticateNeverCalled() {
         verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
@@ -2705,8 +2790,10 @@
     }
 
     private void supportsFaceDetection() throws RemoteException {
+        final boolean isClass3 = !mFaceSensorProperties.isEmpty()
+                && mFaceSensorProperties.get(0).sensorStrength == STRENGTH_STRONG;
         mFaceSensorProperties =
-                List.of(createFaceSensorProperties(/* supportsFaceDetection = */ true));
+                List.of(createFaceSensorProperties(/* supportsFaceDetection = */ true, isClass3));
         mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
     }
 
@@ -2734,7 +2821,7 @@
         }
     }
 
-    private void faceAuthLockedOut() {
+    private void faceAuthLockOut() {
         mKeyguardUpdateMonitor.mFaceAuthenticationCallback
                 .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, "");
     }
@@ -2767,7 +2854,7 @@
         mKeyguardUpdateMonitor.setSwitchingUser(true);
     }
 
-    private void fingerprintErrorTemporaryLockedOut() {
+    private void fingerprintErrorTemporaryLockOut() {
         mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
                 .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT, "Fingerprint locked out");
     }
@@ -2821,18 +2908,18 @@
         );
     }
 
-    private void strongAuthRequiredEncrypted() {
+    private void primaryAuthRequiredEncrypted() {
         when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
                 .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
     }
 
-    private void strongAuthRequiredForWeakBiometricOnly() {
+    private void primaryAuthRequiredForWeakBiometricOnly() {
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(true))).thenReturn(true);
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(false))).thenReturn(false);
     }
 
-    private void strongAuthNotRequired() {
+    private void primaryAuthNotRequiredByStrongAuthTracker() {
         when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
                 .thenReturn(0);
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
@@ -2917,10 +3004,10 @@
     }
 
     private void givenDetectFace() throws RemoteException {
-        // GIVEN bypass is enabled, face detection is supported and strong auth is required
+        // GIVEN bypass is enabled, face detection is supported and primary auth is required
         lockscreenBypassIsAllowed();
         supportsFaceDetection();
-        strongAuthRequiredEncrypted();
+        primaryAuthRequiredEncrypted();
         keyguardIsVisible();
         // fingerprint is NOT running, UDFPS is NOT supported
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
similarity index 85%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt
rename to packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
index 64fec5b..dea2082 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
@@ -13,15 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.shade
+package com.android.keyguard
 
 import android.animation.Animator
 import android.testing.AndroidTestingRunner
 import android.transition.TransitionValues
 import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardStatusViewController
+import com.android.keyguard.KeyguardStatusViewController.SplitShadeTransitionAdapter
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.shade.NotificationPanelViewController.SplitShadeTransitionAdapter
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -33,14 +32,14 @@
 @RunWith(AndroidTestingRunner::class)
 class SplitShadeTransitionAdapterTest : SysuiTestCase() {
 
-    @Mock private lateinit var keyguardStatusViewController: KeyguardStatusViewController
+    @Mock private lateinit var KeyguardClockSwitchController: KeyguardClockSwitchController
 
     private lateinit var adapter: SplitShadeTransitionAdapter
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        adapter = SplitShadeTransitionAdapter(keyguardStatusViewController)
+        adapter = SplitShadeTransitionAdapter(KeyguardClockSwitchController)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ChooserPinMigrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ChooserPinMigrationTest.kt
deleted file mode 100644
index 44da5f4..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/ChooserPinMigrationTest.kt
+++ /dev/null
@@ -1,255 +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
-
-import android.content.Context
-import android.content.Intent
-import android.content.SharedPreferences
-import android.content.res.Resources
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import java.io.File
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class ChooserPinMigrationTest : SysuiTestCase() {
-
-    private val fakeFeatureFlags = FakeFeatureFlags()
-    private val fakePreferences =
-        mutableMapOf(
-            "TestPinnedPackage/TestPinnedClass" to true,
-            "TestUnpinnedPackage/TestUnpinnedClass" to false,
-        )
-    private val intent = kotlinArgumentCaptor<Intent>()
-    private val permission = kotlinArgumentCaptor<String>()
-
-    private lateinit var chooserPinMigration: ChooserPinMigration
-
-    @Mock private lateinit var mockContext: Context
-    @Mock private lateinit var mockResources: Resources
-    @Mock
-    private lateinit var mockLegacyPinPrefsFileSupplier:
-        ChooserPinMigration.Companion.LegacyPinPrefsFileSupplier
-    @Mock private lateinit var mockFile: File
-    @Mock private lateinit var mockSharedPreferences: SharedPreferences
-    @Mock private lateinit var mockSharedPreferencesEditor: SharedPreferences.Editor
-    @Mock private lateinit var mockBroadcastSender: BroadcastSender
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-
-        whenever(mockContext.resources).thenReturn(mockResources)
-        whenever(mockContext.getSharedPreferences(any<File>(), anyInt()))
-            .thenReturn(mockSharedPreferences)
-        whenever(mockResources.getString(anyInt())).thenReturn("TestPackage/TestClass")
-        whenever(mockSharedPreferences.all).thenReturn(fakePreferences)
-        whenever(mockSharedPreferences.edit()).thenReturn(mockSharedPreferencesEditor)
-        whenever(mockSharedPreferencesEditor.commit()).thenReturn(true)
-        whenever(mockLegacyPinPrefsFileSupplier.get()).thenReturn(mockFile)
-        whenever(mockFile.exists()).thenReturn(true)
-        whenever(mockFile.delete()).thenReturn(true)
-        fakeFeatureFlags.set(Flags.CHOOSER_MIGRATION_ENABLED, true)
-    }
-
-    @Test
-    fun start_performsMigration() {
-        // Arrange
-        chooserPinMigration =
-            ChooserPinMigration(
-                mockContext,
-                fakeFeatureFlags,
-                mockBroadcastSender,
-                mockLegacyPinPrefsFileSupplier,
-            )
-
-        // Act
-        chooserPinMigration.start()
-
-        // Assert
-        verify(mockBroadcastSender).sendBroadcast(intent.capture(), permission.capture())
-        assertThat(intent.value.action).isEqualTo("android.intent.action.CHOOSER_PIN_MIGRATION")
-        assertThat(intent.value.`package`).isEqualTo("TestPackage")
-        assertThat(intent.value.extras?.keySet()).hasSize(2)
-        assertThat(intent.value.hasExtra("TestPinnedPackage/TestPinnedClass")).isTrue()
-        assertThat(intent.value.getBooleanExtra("TestPinnedPackage/TestPinnedClass", false))
-            .isTrue()
-        assertThat(intent.value.hasExtra("TestUnpinnedPackage/TestUnpinnedClass")).isTrue()
-        assertThat(intent.value.getBooleanExtra("TestUnpinnedPackage/TestUnpinnedClass", true))
-            .isFalse()
-        assertThat(permission.value).isEqualTo("android.permission.RECEIVE_CHOOSER_PIN_MIGRATION")
-
-        // Assert
-        verify(mockSharedPreferencesEditor).clear()
-        verify(mockSharedPreferencesEditor).commit()
-
-        // Assert
-        verify(mockFile).delete()
-    }
-
-    @Test
-    fun start_doesNotDeleteLegacyPreferencesFile_whenClearingItFails() {
-        // Arrange
-        whenever(mockSharedPreferencesEditor.commit()).thenReturn(false)
-        chooserPinMigration =
-            ChooserPinMigration(
-                mockContext,
-                fakeFeatureFlags,
-                mockBroadcastSender,
-                mockLegacyPinPrefsFileSupplier,
-            )
-
-        // Act
-        chooserPinMigration.start()
-
-        // Assert
-        verify(mockBroadcastSender).sendBroadcast(intent.capture(), permission.capture())
-        assertThat(intent.value.action).isEqualTo("android.intent.action.CHOOSER_PIN_MIGRATION")
-        assertThat(intent.value.`package`).isEqualTo("TestPackage")
-        assertThat(intent.value.extras?.keySet()).hasSize(2)
-        assertThat(intent.value.hasExtra("TestPinnedPackage/TestPinnedClass")).isTrue()
-        assertThat(intent.value.getBooleanExtra("TestPinnedPackage/TestPinnedClass", false))
-            .isTrue()
-        assertThat(intent.value.hasExtra("TestUnpinnedPackage/TestUnpinnedClass")).isTrue()
-        assertThat(intent.value.getBooleanExtra("TestUnpinnedPackage/TestUnpinnedClass", true))
-            .isFalse()
-        assertThat(permission.value).isEqualTo("android.permission.RECEIVE_CHOOSER_PIN_MIGRATION")
-
-        // Assert
-        verify(mockSharedPreferencesEditor).clear()
-        verify(mockSharedPreferencesEditor).commit()
-
-        // Assert
-        verify(mockFile, never()).delete()
-    }
-
-    @Test
-    fun start_OnlyDeletesLegacyPreferencesFile_whenEmpty() {
-        // Arrange
-        whenever(mockSharedPreferences.all).thenReturn(emptyMap())
-        chooserPinMigration =
-            ChooserPinMigration(
-                mockContext,
-                fakeFeatureFlags,
-                mockBroadcastSender,
-                mockLegacyPinPrefsFileSupplier,
-            )
-
-        // Act
-        chooserPinMigration.start()
-
-        // Assert
-        verifyZeroInteractions(mockBroadcastSender)
-
-        // Assert
-        verifyZeroInteractions(mockSharedPreferencesEditor)
-
-        // Assert
-        verify(mockFile).delete()
-    }
-
-    @Test
-    fun start_DoesNotDoMigration_whenFlagIsDisabled() {
-        // Arrange
-        fakeFeatureFlags.set(Flags.CHOOSER_MIGRATION_ENABLED, false)
-        chooserPinMigration =
-            ChooserPinMigration(
-                mockContext,
-                fakeFeatureFlags,
-                mockBroadcastSender,
-                mockLegacyPinPrefsFileSupplier,
-            )
-
-        // Act
-        chooserPinMigration.start()
-
-        // Assert
-        verifyZeroInteractions(mockBroadcastSender)
-
-        // Assert
-        verifyZeroInteractions(mockSharedPreferencesEditor)
-
-        // Assert
-        verify(mockFile, never()).delete()
-    }
-
-    @Test
-    fun start_DoesNotDoMigration_whenLegacyPreferenceFileNotPresent() {
-        // Arrange
-        whenever(mockFile.exists()).thenReturn(false)
-        chooserPinMigration =
-            ChooserPinMigration(
-                mockContext,
-                fakeFeatureFlags,
-                mockBroadcastSender,
-                mockLegacyPinPrefsFileSupplier,
-            )
-
-        // Act
-        chooserPinMigration.start()
-
-        // Assert
-        verifyZeroInteractions(mockBroadcastSender)
-
-        // Assert
-        verifyZeroInteractions(mockSharedPreferencesEditor)
-
-        // Assert
-        verify(mockFile, never()).delete()
-    }
-
-    @Test
-    fun start_DoesNotDoMigration_whenConfiguredChooserComponentIsInvalid() {
-        // Arrange
-        whenever(mockResources.getString(anyInt())).thenReturn("InvalidComponent")
-        chooserPinMigration =
-            ChooserPinMigration(
-                mockContext,
-                fakeFeatureFlags,
-                mockBroadcastSender,
-                mockLegacyPinPrefsFileSupplier,
-            )
-
-        // Act
-        chooserPinMigration.start()
-
-        // Assert
-        verifyZeroInteractions(mockBroadcastSender)
-
-        // Assert
-        verifyZeroInteractions(mockSharedPreferencesEditor)
-
-        // Assert
-        verify(mockFile, never()).delete()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
new file mode 100644
index 0000000..01d3a39
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui
+
+import android.graphics.Point
+import android.hardware.display.DisplayManagerGlobal
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayInfo
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.decor.FaceScanningProviderFactory
+import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.log.ScreenDecorationsLogger
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FaceScanningProviderFactoryTest : SysuiTestCase() {
+
+    private lateinit var underTest: FaceScanningProviderFactory
+
+    @Mock private lateinit var authController: AuthController
+
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+    @Mock private lateinit var display: Display
+
+    private val displayId = 2
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        val displayInfo = DisplayInfo()
+        val dmGlobal = mock(DisplayManagerGlobal::class.java)
+        val display =
+            Display(
+                dmGlobal,
+                displayId,
+                displayInfo,
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+            )
+        whenever(dmGlobal.getDisplayInfo(eq(displayId))).thenReturn(displayInfo)
+        val displayContext = context.createDisplayContext(display) as SysuiTestableContext
+        displayContext.orCreateTestableResources.addOverride(
+            R.array.config_displayUniqueIdArray,
+            arrayOf(displayId)
+        )
+        displayContext.orCreateTestableResources.addOverride(
+            R.bool.config_fillMainBuiltInDisplayCutout,
+            true
+        )
+        underTest =
+            FaceScanningProviderFactory(
+                authController,
+                displayContext,
+                statusBarStateController,
+                keyguardUpdateMonitor,
+                mock(Executor::class.java),
+                ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest"))
+            )
+
+        whenever(authController.faceSensorLocation).thenReturn(Point(10, 10))
+    }
+
+    @Test
+    fun shouldNotShowFaceScanningAnimationIfFaceIsNotEnrolled() {
+        whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false)
+        whenever(authController.isShowing).thenReturn(true)
+
+        assertThat(underTest.shouldShowFaceScanningAnim()).isFalse()
+    }
+
+    @Test
+    fun shouldShowFaceScanningAnimationIfBiometricPromptIsShowing() {
+        whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
+        whenever(authController.isShowing).thenReturn(true)
+
+        assertThat(underTest.shouldShowFaceScanningAnim()).isTrue()
+    }
+
+    @Test
+    fun shouldShowFaceScanningAnimationIfKeyguardFaceDetectionIsShowing() {
+        whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
+        whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true)
+
+        assertThat(underTest.shouldShowFaceScanningAnim()).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index edee3f1..64c028e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -1017,7 +1017,7 @@
             // THEN the display should be unconfigured once. If the timeout action is not
             // cancelled, the display would be unconfigured twice which would cause two
             // FP attempts.
-            verify(mUdfpsView, times(1)).unconfigureDisplay();
+            verify(mUdfpsView).unconfigureDisplay();
         } else {
             verify(mUdfpsView, never()).unconfigureDisplay();
         }
@@ -1301,8 +1301,8 @@
         mBiometricExecutor.runAllReady();
         downEvent.recycle();
 
-        // THEN the touch is pilfered, expected twice (valid overlap and touch on sensor)
-        verify(mInputManager, times(2)).pilferPointers(any());
+        // THEN the touch is pilfered
+        verify(mInputManager).pilferPointers(any());
     }
 
     @Test
@@ -1340,7 +1340,7 @@
         downEvent.recycle();
 
         // THEN the touch is NOT pilfered
-        verify(mInputManager, times(0)).pilferPointers(any());
+        verify(mInputManager, never()).pilferPointers(any());
     }
 
     @Test
@@ -1380,7 +1380,51 @@
         downEvent.recycle();
 
         // THEN the touch is pilfered
-        verify(mInputManager, times(1)).pilferPointers(any());
+        verify(mInputManager).pilferPointers(any());
+    }
+
+    @Test
+    public void onTouch_withNewTouchDetection_doNotPilferWhenPullingUpBouncer()
+            throws RemoteException {
+        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+                0L);
+        final TouchProcessorResult processorResultMove =
+                new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN,
+                        1 /* pointerId */, touchData);
+
+        // Enable new touch detection.
+        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
+
+        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
+        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
+
+        // Configure UdfpsView to accept the ACTION_MOVE event
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+        // GIVEN that the alternate bouncer is not showing and a11y touch exploration NOT enabled
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+        // GIVEN a swipe up to bring up primary bouncer is in progress or swipe down for QS
+        when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true);
+        when(mLockscreenShadeTransitionController.getFractionToShade()).thenReturn(1f);
+
+        // WHEN ACTION_MOVE is received and touch is within sensor
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorResultMove);
+        MotionEvent moveEvent = MotionEvent.obtain(0, 0, ACTION_MOVE, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+        mBiometricExecutor.runAllReady();
+        moveEvent.recycle();
+
+        // THEN the touch is NOT pilfered
+        verify(mInputManager, never()).pilferPointers(any());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index 8cb9130..4cb99a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -120,7 +120,6 @@
                 gestureCompleteListenerCaptor.capture());
 
         mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue();
-        mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
         mFakeFeatureFlags.set(Flags.MEDIA_FALSING_PENALTY, true);
         mFakeFeatureFlags.set(Flags.FALSING_OFF_FOR_UNFOLDED, true);
     }
@@ -260,13 +259,6 @@
     }
 
     @Test
-    public void testIsFalseLongTap_FalseLongTap_NotFlagged() {
-        mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, false);
-        when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mFalsedResult);
-        assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse();
-    }
-
-    @Test
     public void testIsFalseLongTap_FalseLongTap() {
         when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mFalsedResult);
         assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index 315774a..292fdff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -94,7 +94,6 @@
                 mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier,
                 mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
                 mAccessibilityManager, false, mFakeFeatureFlags);
-        mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
         mFakeFeatureFlags.set(Flags.FALSING_OFF_FOR_UNFOLDED, true);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index fd6e31b..1851582 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.clipboardoverlay;
 
-import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
-
 import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
 
 import static org.junit.Assert.assertEquals;
@@ -40,7 +38,6 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FakeFeatureFlags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -65,7 +62,6 @@
     private ClipboardOverlayController mOverlayController;
     @Mock
     private ClipboardToast mClipboardToast;
-    private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     @Mock
     private UiEventLogger mUiEventLogger;
 
@@ -99,10 +95,8 @@
         when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData);
         when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
 
-        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, true);
-
         mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider,
-                mClipboardToast, mClipboardManager, mFeatureFlags, mUiEventLogger);
+                mClipboardToast, mClipboardManager, mUiEventLogger);
     }
 
 
@@ -222,34 +216,4 @@
         verify(mClipboardToast, times(1)).showCopiedToast();
         verifyZeroInteractions(mOverlayControllerProvider);
     }
-
-    @Test
-    public void test_minimizedLayoutFlagOff_usesLegacy() {
-        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-
-        mClipboardListener.start();
-        mClipboardListener.onPrimaryClipChanged();
-
-        verify(mOverlayControllerProvider).get();
-
-        verify(mOverlayController).setClipDataLegacy(
-                mClipDataCaptor.capture(), mStringCaptor.capture());
-
-        assertEquals(mSampleClipData, mClipDataCaptor.getValue());
-        assertEquals(mSampleSource, mStringCaptor.getValue());
-    }
-
-    @Test
-    public void test_minimizedLayoutFlagOn_usesNew() {
-        mClipboardListener.start();
-        mClipboardListener.onPrimaryClipChanged();
-
-        verify(mOverlayControllerProvider).get();
-
-        verify(mOverlayController).setClipData(
-                mClipDataCaptor.capture(), mStringCaptor.capture());
-
-        assertEquals(mSampleClipData, mClipDataCaptor.getValue());
-        assertEquals(mSampleSource, mStringCaptor.getValue());
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index 299869c..8600b7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -25,7 +25,6 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_EXPANDED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_MINIMIZED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
-import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
 import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -123,7 +122,6 @@
                 new ClipData.Item("Test Item"));
 
         mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, false);
-        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, true); // turned off for legacy tests
 
         mOverlayController = new ClipboardOverlayController(
                 mContext,
@@ -146,178 +144,6 @@
     }
 
     @Test
-    public void test_setClipData_invalidImageData_legacy() {
-        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-        ClipData clipData = new ClipData("", new String[]{"image/png"},
-                new ClipData.Item(Uri.parse("")));
-
-        mOverlayController.setClipDataLegacy(clipData, "");
-
-        verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
-        verify(mClipboardOverlayView, times(1)).showShareChip();
-        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
-    }
-
-    @Test
-    public void test_setClipData_nonImageUri_legacy() {
-        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-        ClipData clipData = new ClipData("", new String[]{"resource/png"},
-                new ClipData.Item(Uri.parse("")));
-
-        mOverlayController.setClipDataLegacy(clipData, "");
-
-        verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
-        verify(mClipboardOverlayView, times(1)).showShareChip();
-        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
-    }
-
-    @Test
-    public void test_setClipData_textData_legacy() {
-        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-
-        mOverlayController.setClipDataLegacy(mSampleClipData, "");
-
-        verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
-        verify(mClipboardOverlayView, times(1)).showShareChip();
-        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
-    }
-
-    @Test
-    public void test_setClipData_sensitiveTextData_legacy() {
-        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-
-        ClipDescription description = mSampleClipData.getDescription();
-        PersistableBundle b = new PersistableBundle();
-        b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
-        description.setExtras(b);
-        ClipData data = new ClipData(description, mSampleClipData.getItemAt(0));
-        mOverlayController.setClipDataLegacy(data, "");
-
-        verify(mClipboardOverlayView, times(1)).showTextPreview("••••••", true);
-        verify(mClipboardOverlayView, times(1)).showShareChip();
-        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
-    }
-
-    @Test
-    public void test_setClipData_repeatedCalls_legacy() {
-        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-        when(mAnimator.isRunning()).thenReturn(true);
-
-        mOverlayController.setClipDataLegacy(mSampleClipData, "");
-        mOverlayController.setClipDataLegacy(mSampleClipData, "");
-
-        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
-    }
-
-    @Test
-    public void test_viewCallbacks_onShareTapped_legacy() {
-        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-        mOverlayController.setClipDataLegacy(mSampleClipData, "");
-
-        mCallbacks.onShareButtonTapped();
-
-        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "");
-        verify(mClipboardOverlayView, times(1)).getExitAnimation();
-    }
-
-    @Test
-    public void test_viewCallbacks_onDismissTapped_legacy() {
-        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-        mOverlayController.setClipDataLegacy(mSampleClipData, "");
-
-        mCallbacks.onDismissButtonTapped();
-
-        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "");
-        verify(mClipboardOverlayView, times(1)).getExitAnimation();
-    }
-
-    @Test
-    public void test_multipleDismissals_dismissesOnce_legacy() {
-        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-
-        mCallbacks.onSwipeDismissInitiated(mAnimator);
-        mCallbacks.onDismissButtonTapped();
-        mCallbacks.onSwipeDismissInitiated(mAnimator);
-        mCallbacks.onDismissButtonTapped();
-
-        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED, 0, null);
-        verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
-    }
-
-    @Test
-    public void test_remoteCopy_withFlagOn_legacy() {
-        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-        mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
-        when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
-
-        mOverlayController.setClipDataLegacy(mSampleClipData, "");
-
-        verify(mTimeoutHandler, never()).resetTimeout();
-    }
-
-    @Test
-    public void test_remoteCopy_withFlagOff_legacy() {
-        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-        when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
-
-        mOverlayController.setClipDataLegacy(mSampleClipData, "");
-
-        verify(mTimeoutHandler).resetTimeout();
-    }
-
-    @Test
-    public void test_nonRemoteCopy_legacy() {
-        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-        mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
-        when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(false);
-
-        mOverlayController.setClipDataLegacy(mSampleClipData, "");
-
-        verify(mTimeoutHandler).resetTimeout();
-    }
-
-    @Test
-    public void test_logsUseLastClipSource_legacy() {
-        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-
-        mOverlayController.setClipDataLegacy(mSampleClipData, "first.package");
-        mCallbacks.onDismissButtonTapped();
-        mOverlayController.setClipDataLegacy(mSampleClipData, "second.package");
-        mCallbacks.onDismissButtonTapped();
-
-        verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "first.package");
-        verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "second.package");
-        verifyNoMoreInteractions(mUiEventLogger);
-    }
-
-    @Test
-    public void test_logOnClipboardActionsShown_legacy() {
-        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-        ClipData.Item item = mSampleClipData.getItemAt(0);
-        item.setTextLinks(Mockito.mock(TextLinks.class));
-        mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
-        when(mClipboardUtils.isRemoteCopy(any(Context.class), any(ClipData.class), anyString()))
-                .thenReturn(true);
-        when(mClipboardUtils.getAction(any(ClipData.Item.class), anyString()))
-                .thenReturn(Optional.of(Mockito.mock(RemoteAction.class)));
-        when(mClipboardOverlayView.post(any(Runnable.class))).thenAnswer(new Answer<Object>() {
-            @Override
-            public Object answer(InvocationOnMock invocation) throws Throwable {
-                ((Runnable) invocation.getArgument(0)).run();
-                return null;
-            }
-        });
-
-        mOverlayController.setClipDataLegacy(
-                new ClipData(mSampleClipData.getDescription(), item), "actionShownSource");
-        mExecutor.runAllReady();
-
-        verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_ACTION_SHOWN, 0, "actionShownSource");
-        verifyNoMoreInteractions(mUiEventLogger);
-    }
-
-    // start of refactored setClipData tests
-    @Test
     public void test_setClipData_invalidImageData() {
         ClipData clipData = new ClipData("", new String[]{"image/png"},
                 new ClipData.Item(Uri.parse("")));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
index 3d8f04e..673b5eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
@@ -142,81 +142,6 @@
         assertEquals(actionB, result);
     }
 
-    // TODO(b/267162944): Next four tests (marked "legacy") are obsolete once
-    //  CLIPBOARD_MINIMIZED_LAYOUT flag is released and removed
-    @Test
-    public void test_getAction_noLinks_returnsEmptyOptional_legacy() {
-        ClipData.Item item = new ClipData.Item("no text links");
-        item.setTextLinks(Mockito.mock(TextLinks.class));
-
-        Optional<RemoteAction> action = mClipboardUtils.getAction(item, "");
-
-        assertTrue(action.isEmpty());
-    }
-
-    @Test
-    public void test_getAction_returnsFirstLink_legacy() {
-        when(mClipDataItem.getTextLinks()).thenReturn(getFakeTextLinksBuilder().build());
-        when(mClipDataItem.getText()).thenReturn("");
-        RemoteAction actionA = constructRemoteAction("abc");
-        RemoteAction actionB = constructRemoteAction("def");
-        TextClassification classificationA = Mockito.mock(TextClassification.class);
-        when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA));
-        TextClassification classificationB = Mockito.mock(TextClassification.class);
-        when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB));
-        when(mTextClassifier.classifyText(anyString(), anyInt(), anyInt(), isNull())).thenReturn(
-                classificationA, classificationB);
-
-        RemoteAction result = mClipboardUtils.getAction(mClipDataItem, "test").orElse(null);
-
-        assertEquals(actionA, result);
-    }
-
-    @Test
-    public void test_getAction_skipsMatchingComponent_legacy() {
-        when(mClipDataItem.getTextLinks()).thenReturn(getFakeTextLinksBuilder().build());
-        when(mClipDataItem.getText()).thenReturn("");
-        RemoteAction actionA = constructRemoteAction("abc");
-        RemoteAction actionB = constructRemoteAction("def");
-        TextClassification classificationA = Mockito.mock(TextClassification.class);
-        when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA));
-        TextClassification classificationB = Mockito.mock(TextClassification.class);
-        when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB));
-        when(mTextClassifier.classifyText(anyString(), anyInt(), anyInt(), isNull())).thenReturn(
-                classificationA, classificationB);
-
-        RemoteAction result = mClipboardUtils.getAction(mClipDataItem, "abc").orElse(null);
-
-        assertEquals(actionB, result);
-    }
-
-    @Test
-    public void test_getAction_skipsShortEntity_legacy() {
-        TextLinks.Builder textLinks = new TextLinks.Builder("test text of length 22");
-        final Map<String, Float> scores = new ArrayMap<>();
-        scores.put(TextClassifier.TYPE_EMAIL, 1f);
-        textLinks.addLink(20, 22, scores);
-        textLinks.addLink(0, 22, scores);
-
-        when(mClipDataItem.getTextLinks()).thenReturn(textLinks.build());
-        when(mClipDataItem.getText()).thenReturn(textLinks.build().getText());
-
-        RemoteAction actionA = constructRemoteAction("abc");
-        RemoteAction actionB = constructRemoteAction("def");
-        TextClassification classificationA = Mockito.mock(TextClassification.class);
-        when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA));
-        TextClassification classificationB = Mockito.mock(TextClassification.class);
-        when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB));
-        when(mTextClassifier.classifyText(anyString(), eq(20), eq(22), isNull())).thenReturn(
-                classificationA);
-        when(mTextClassifier.classifyText(anyString(), eq(0), eq(22), isNull())).thenReturn(
-                classificationB);
-
-        RemoteAction result = mClipboardUtils.getAction(mClipDataItem, "test").orElse(null);
-
-        assertEquals(actionB, result);
-    }
-
     @Test
     public void test_extra_withPackage_returnsTrue() {
         PersistableBundle b = new PersistableBundle();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
index 5fcf414..8fdc491 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
@@ -18,8 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -29,23 +29,45 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
 import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
 
 import java.util.Collection;
 import java.util.HashSet;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class ComplicationCollectionLiveDataTest extends SysuiTestCase {
+
+    private FakeExecutor mExecutor;
+    private DreamOverlayStateController mStateController;
+    private ComplicationCollectionLiveData mLiveData;
+    private FakeFeatureFlags mFeatureFlags;
+    @Mock
+    private Observer mObserver;
+
     @Before
-    public void setUp() throws Exception {
-        allowTestableLooperAsMainThread();
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mFeatureFlags = new FakeFeatureFlags();
+        mExecutor = new FakeExecutor(new FakeSystemClock());
+        mFeatureFlags.set(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS, true);
+        mStateController = new DreamOverlayStateController(
+                mExecutor,
+                /* overlayEnabled= */ true,
+                mFeatureFlags);
+        mLiveData = new ComplicationCollectionLiveData(mStateController);
     }
 
     @Test
@@ -53,45 +75,41 @@
      * Ensures registration and callback lifecycles are respected.
      */
     public void testLifecycle() {
-        getContext().getMainExecutor().execute(() -> {
-            final DreamOverlayStateController stateController =
-                    Mockito.mock(DreamOverlayStateController.class);
-            final ComplicationCollectionLiveData liveData =
-                    new ComplicationCollectionLiveData(stateController);
-            final HashSet<Complication> complications = new HashSet<>();
-            final Observer<Collection<Complication>> observer = Mockito.mock(Observer.class);
-            complications.add(Mockito.mock(Complication.class));
+        final HashSet<Complication> complications = new HashSet<>();
+        mLiveData.observeForever(mObserver);
+        mExecutor.runAllReady();
+        // Verify observer called with empty complications
+        assertObserverCalledWith(complications);
 
-            when(stateController.getComplications()).thenReturn(complications);
+        addComplication(mock(Complication.class), complications);
+        assertObserverCalledWith(complications);
 
-            liveData.observeForever(observer);
-            ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor =
-                    ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+        addComplication(mock(Complication.class), complications);
+        assertObserverCalledWith(complications);
 
-            verify(stateController).addCallback(callbackCaptor.capture());
-            verifyUpdate(observer, complications);
-
-            complications.add(Mockito.mock(Complication.class));
-            callbackCaptor.getValue().onComplicationsChanged();
-
-            verifyUpdate(observer, complications);
-
-            callbackCaptor.getValue().onAvailableComplicationTypesChanged();
-
-            verifyUpdate(observer, complications);
-        });
+        mStateController.setAvailableComplicationTypes(0);
+        mExecutor.runAllReady();
+        assertObserverCalledWith(complications);
+        mLiveData.removeObserver(mObserver);
     }
 
-    void verifyUpdate(Observer<Collection<Complication>> observer,
-            Collection<Complication> targetCollection) {
+    private void assertObserverCalledWith(Collection<Complication> targetCollection) {
         ArgumentCaptor<Collection<Complication>> collectionCaptor =
                 ArgumentCaptor.forClass(Collection.class);
 
-        verify(observer).onChanged(collectionCaptor.capture());
+        verify(mObserver).onChanged(collectionCaptor.capture());
 
-        final Collection collection =  collectionCaptor.getValue();
+        final Collection<Complication> collection = collectionCaptor.getValue();
+
         assertThat(collection.containsAll(targetCollection)
                 && targetCollection.containsAll(collection)).isTrue();
-        Mockito.clearInvocations(observer);
+        Mockito.clearInvocations(mObserver);
+    }
+
+    private void addComplication(Complication complication,
+            Collection<Complication> complications) {
+        complications.add(complication);
+        mStateController.addComplication(complication);
+        mExecutor.runAllReady();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index b00b273..15fca5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -73,6 +73,8 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
@@ -156,6 +158,8 @@
     /** Most recent value passed to {@link KeyguardStateController#notifyKeyguardGoingAway}. */
     private boolean mKeyguardGoingAway = false;
 
+    private FakeFeatureFlags mFeatureFlags;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -174,6 +178,8 @@
                 mColorExtractor, mDumpManager, mKeyguardStateController,
                 mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager,
                 mShadeWindowLogger);
+        mFeatureFlags = new FakeFeatureFlags();
+
 
         DejankUtils.setImmediate(true);
 
@@ -628,6 +634,28 @@
                 .setLockScreenShown(eq(true), anyBoolean());
     }
 
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    public void testNotStartingKeyguardWhenFlagIsDisabled() {
+        mViewMediator.setShowingLocked(false);
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+        mFeatureFlags.set(Flags.LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING, false);
+        mViewMediator.onDreamingStarted();
+        assertFalse(mViewMediator.isShowingAndNotOccluded());
+    }
+
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    public void testStartingKeyguardWhenFlagIsEnabled() {
+        mViewMediator.setShowingLocked(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+
+        mFeatureFlags.set(Flags.LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING, true);
+        mViewMediator.onDreamingStarted();
+        assertTrue(mViewMediator.isShowingAndNotOccluded());
+    }
+
     private void createAndStartViewMediator() {
         mViewMediator = new KeyguardViewMediator(
                 mContext,
@@ -659,7 +687,8 @@
                 () -> mNotificationShadeWindowController,
                 () -> mActivityLaunchAnimator,
                 () -> mScrimController,
-                mActivityTaskManagerService);
+                mActivityTaskManagerService,
+                mFeatureFlags);
         mViewMediator.start();
 
         mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 6e002f5..2489e04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -59,11 +59,10 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.util.mockito.captureMany
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.SystemClock
 import com.google.common.truth.Truth.assertThat
-import java.io.PrintWriter
-import java.io.StringWriter
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -81,6 +80,7 @@
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.isNull
 import org.mockito.Mockito.mock
@@ -88,6 +88,8 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
+import java.io.PrintWriter
+import java.io.StringWriter
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -120,6 +122,7 @@
     private lateinit var authStatus: FlowValue<AuthenticationStatus?>
     private lateinit var detectStatus: FlowValue<DetectionStatus?>
     private lateinit var authRunning: FlowValue<Boolean?>
+    private lateinit var bypassEnabled: FlowValue<Boolean?>
     private lateinit var lockedOut: FlowValue<Boolean?>
     private lateinit var canFaceAuthRun: FlowValue<Boolean?>
     private lateinit var authenticated: FlowValue<Boolean?>
@@ -726,6 +729,23 @@
         }
 
     @Test
+    fun isBypassEnabledReflectsBypassControllerState() =
+        testScope.runTest {
+            initCollectors()
+            runCurrent()
+            val listeners = captureMany {
+                verify(bypassController, atLeastOnce())
+                    .registerOnBypassStateChangedListener(capture())
+            }
+
+            listeners.forEach { it.onBypassStateChanged(true) }
+            assertThat(bypassEnabled()).isTrue()
+
+            listeners.forEach { it.onBypassStateChanged(false) }
+            assertThat(bypassEnabled()).isFalse()
+        }
+
+    @Test
     fun detectDoesNotRunWhenNonStrongBiometricIsAllowed() =
         testScope.runTest {
             testGatingCheckForDetect {
@@ -844,6 +864,7 @@
         lockedOut = collectLastValue(underTest.isLockedOut)
         canFaceAuthRun = collectLastValue(underTest.canRunFaceAuth)
         authenticated = collectLastValue(underTest.isAuthenticated)
+        bypassEnabled = collectLastValue(underTest.isBypassEnabled)
         fakeUserRepository.setSelectedUserInfo(primaryUser)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 86e8c9a..a668af3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -41,6 +41,7 @@
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
+import java.util.Locale
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
@@ -67,6 +68,7 @@
 
     @Before
     fun setUp() {
+        context.resources.configuration.setLayoutDirection(Locale.US)
         config1 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_1)
         config2 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_2)
         val testDispatcher = StandardTestDispatcher()
@@ -222,6 +224,40 @@
     }
 
     @Test
+    fun getSlotPickerRepresentations_rightToLeft_slotsReversed() {
+        context.resources.configuration.setLayoutDirection(Locale("he", "IL"))
+        val slot1 = "slot1"
+        val slot2 = "slot2"
+        val slot3 = "slot3"
+        context.orCreateTestableResources.addOverride(
+            R.array.config_keyguardQuickAffordanceSlots,
+            arrayOf(
+                "$slot1:2",
+                "$slot2:4",
+                "$slot3:5",
+            ),
+        )
+
+        assertThat(underTest.getSlotPickerRepresentations())
+            .isEqualTo(
+                listOf(
+                    KeyguardSlotPickerRepresentation(
+                        id = slot3,
+                        maxSelectedAffordances = 5,
+                    ),
+                    KeyguardSlotPickerRepresentation(
+                        id = slot2,
+                        maxSelectedAffordances = 4,
+                    ),
+                    KeyguardSlotPickerRepresentation(
+                        id = slot1,
+                        maxSelectedAffordances = 2,
+                    ),
+                )
+            )
+    }
+
+    @Test
     fun `selections for secondary user`() =
         testScope.runTest {
             userTracker.set(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
index 51988ef..77bb12c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
@@ -29,20 +29,20 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.util.mockito.any
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
-import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -51,8 +51,8 @@
 @RunWith(AndroidJUnit4::class)
 class KeyguardLongPressInteractorTest : SysuiTestCase() {
 
-    @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var logger: UiEventLogger
+    @Mock private lateinit var accessibilityManager: AccessibilityManagerWrapper
 
     private lateinit var underTest: KeyguardLongPressInteractor
 
@@ -63,6 +63,14 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        whenever(accessibilityManager.getRecommendedTimeoutMillis(anyInt(), anyInt())).thenAnswer {
+            it.arguments[0]
+        }
+
+        testScope = TestScope()
+        keyguardRepository = FakeKeyguardRepository()
+        keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+
         runBlocking { createUnderTest() }
     }
 
@@ -98,60 +106,117 @@
         }
 
     @Test
-    fun `long-pressed - pop-up clicked - starts activity`() =
+    fun longPressed_menuClicked_showsSettings() =
         testScope.runTest {
-            val menu = collectLastValue(underTest.menu)
+            val isMenuVisible by collectLastValue(underTest.isMenuVisible)
+            val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings)
             runCurrent()
 
-            val x = 100
-            val y = 123
-            underTest.onLongPress(x, y)
-            assertThat(menu()).isNotNull()
-            assertThat(menu()?.position?.x).isEqualTo(x)
-            assertThat(menu()?.position?.y).isEqualTo(y)
+            underTest.onLongPress()
+            assertThat(isMenuVisible).isTrue()
 
-            menu()?.onClicked?.invoke()
+            underTest.onMenuTouchGestureEnded(/* isClick= */ true)
 
-            assertThat(menu()).isNull()
-            verify(activityStarter).dismissKeyguardThenExecute(any(), any(), anyBoolean())
+            assertThat(isMenuVisible).isFalse()
+            assertThat(shouldOpenSettings).isTrue()
         }
 
     @Test
-    fun `long-pressed - pop-up dismissed - never starts activity`() =
+    fun onSettingsShown_consumesSettingsShowEvent() =
         testScope.runTest {
-            val menu = collectLastValue(underTest.menu)
+            val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings)
             runCurrent()
 
-            menu()?.onDismissed?.invoke()
+            underTest.onLongPress()
+            underTest.onMenuTouchGestureEnded(/* isClick= */ true)
+            assertThat(shouldOpenSettings).isTrue()
 
-            assertThat(menu()).isNull()
-            verify(activityStarter, never()).dismissKeyguardThenExecute(any(), any(), anyBoolean())
+            underTest.onSettingsShown()
+            assertThat(shouldOpenSettings).isFalse()
         }
 
-    @Suppress("DEPRECATION") // We're okay using ACTION_CLOSE_SYSTEM_DIALOGS on system UI.
+    @Test
+    fun onTouchedOutside_neverShowsSettings() =
+        testScope.runTest {
+            val isMenuVisible by collectLastValue(underTest.isMenuVisible)
+            val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings)
+            runCurrent()
+
+            underTest.onTouchedOutside()
+
+            assertThat(isMenuVisible).isFalse()
+            assertThat(shouldOpenSettings).isFalse()
+        }
+
+    @Test
+    fun longPressed_openWppDirectlyEnabled_doesNotShowMenu_opensSettings() =
+        testScope.runTest {
+            createUnderTest(isOpenWppDirectlyEnabled = true)
+            val isMenuVisible by collectLastValue(underTest.isMenuVisible)
+            val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings)
+            runCurrent()
+
+            underTest.onLongPress()
+
+            assertThat(isMenuVisible).isFalse()
+            assertThat(shouldOpenSettings).isTrue()
+        }
+
     @Test
     fun `long pressed - close dialogs broadcast received - popup dismissed`() =
         testScope.runTest {
-            val menu = collectLastValue(underTest.menu)
+            val isMenuVisible by collectLastValue(underTest.isMenuVisible)
             runCurrent()
 
-            underTest.onLongPress(123, 456)
-            assertThat(menu()).isNotNull()
+            underTest.onLongPress()
+            assertThat(isMenuVisible).isTrue()
 
             fakeBroadcastDispatcher.registeredReceivers.forEach { broadcastReceiver ->
                 broadcastReceiver.onReceive(context, Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
             }
 
-            assertThat(menu()).isNull()
+            assertThat(isMenuVisible).isFalse()
+        }
+
+    @Test
+    fun closesDialogAfterTimeout() =
+        testScope.runTest {
+            val isMenuVisible by collectLastValue(underTest.isMenuVisible)
+            runCurrent()
+
+            underTest.onLongPress()
+            assertThat(isMenuVisible).isTrue()
+
+            advanceTimeBy(KeyguardLongPressInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS)
+
+            assertThat(isMenuVisible).isFalse()
+        }
+
+    @Test
+    fun closesDialogAfterTimeout_onlyAfterTouchGestureEnded() =
+        testScope.runTest {
+            val isMenuVisible by collectLastValue(underTest.isMenuVisible)
+            runCurrent()
+
+            underTest.onLongPress()
+            assertThat(isMenuVisible).isTrue()
+            underTest.onMenuTouchGestureStarted()
+
+            advanceTimeBy(KeyguardLongPressInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS)
+            assertThat(isMenuVisible).isTrue()
+
+            underTest.onMenuTouchGestureEnded(/* isClick= */ false)
+            advanceTimeBy(KeyguardLongPressInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS)
+            assertThat(isMenuVisible).isFalse()
         }
 
     @Test
     fun `logs when menu is shown`() =
         testScope.runTest {
-            collectLastValue(underTest.menu)
+            collectLastValue(underTest.isMenuVisible)
             runCurrent()
 
-            underTest.onLongPress(100, 123)
+            underTest.onLongPress()
 
             verify(logger)
                 .log(KeyguardLongPressInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN)
@@ -160,41 +225,61 @@
     @Test
     fun `logs when menu is clicked`() =
         testScope.runTest {
-            val menu = collectLastValue(underTest.menu)
+            collectLastValue(underTest.isMenuVisible)
             runCurrent()
 
-            underTest.onLongPress(100, 123)
-            menu()?.onClicked?.invoke()
+            underTest.onLongPress()
+            underTest.onMenuTouchGestureEnded(/* isClick= */ true)
 
             verify(logger)
                 .log(KeyguardLongPressInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED)
         }
 
+    @Test
+    fun showMenu_leaveLockscreen_returnToLockscreen_menuNotVisible() =
+        testScope.runTest {
+            val isMenuVisible by collectLastValue(underTest.isMenuVisible)
+            runCurrent()
+            underTest.onLongPress()
+            assertThat(isMenuVisible).isTrue()
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    to = KeyguardState.GONE,
+                ),
+            )
+            assertThat(isMenuVisible).isFalse()
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    to = KeyguardState.LOCKSCREEN,
+                ),
+            )
+            assertThat(isMenuVisible).isFalse()
+        }
+
     private suspend fun createUnderTest(
         isLongPressFeatureEnabled: Boolean = true,
         isRevampedWppFeatureEnabled: Boolean = true,
+        isOpenWppDirectlyEnabled: Boolean = false,
     ) {
-        testScope = TestScope()
-        keyguardRepository = FakeKeyguardRepository()
-        keyguardTransitionRepository = FakeKeyguardTransitionRepository()
-
         underTest =
             KeyguardLongPressInteractor(
-                unsafeContext = context,
                 scope = testScope.backgroundScope,
                 transitionInteractor =
                     KeyguardTransitionInteractor(
                         repository = keyguardTransitionRepository,
                     ),
                 repository = keyguardRepository,
-                activityStarter = activityStarter,
                 logger = logger,
                 featureFlags =
                     FakeFeatureFlags().apply {
                         set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, isLongPressFeatureEnabled)
                         set(Flags.REVAMPED_WALLPAPER_UI, isRevampedWppFeatureEnabled)
+                        set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, isOpenWppDirectlyEnabled)
                     },
                 broadcastDispatcher = fakeBroadcastDispatcher,
+                accessibilityManager = accessibilityManager
             )
         setUpState()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index bfc09d7..224eec1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -20,10 +20,12 @@
 import android.content.Intent
 import android.os.UserHandle
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.animation.Expandable
+import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.doze.util.BurnInHelperWrapper
@@ -38,10 +40,13 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
 import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
@@ -51,6 +56,7 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.any
@@ -91,6 +97,8 @@
     @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
     @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock private lateinit var accessibilityManager: AccessibilityManagerWrapper
 
     private lateinit var underTest: KeyguardBottomAreaViewModel
 
@@ -134,6 +142,8 @@
             FakeFeatureFlags().apply {
                 set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
                 set(Flags.FACE_AUTH_REFACTOR, true)
+                set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
+                set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false)
             }
 
         val keyguardInteractor =
@@ -196,6 +206,19 @@
                 dumpManager = mock(),
                 userHandle = UserHandle.SYSTEM,
             )
+        val keyguardLongPressInteractor =
+            KeyguardLongPressInteractor(
+                scope = testScope.backgroundScope,
+                transitionInteractor =
+                    KeyguardTransitionInteractor(
+                        repository = FakeKeyguardTransitionRepository(),
+                    ),
+                repository = repository,
+                logger = UiEventLoggerFake(),
+                featureFlags = featureFlags,
+                broadcastDispatcher = broadcastDispatcher,
+                accessibilityManager = accessibilityManager,
+            )
         underTest =
             KeyguardBottomAreaViewModel(
                 keyguardInteractor = keyguardInteractor,
@@ -216,6 +239,14 @@
                     ),
                 bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
                 burnInHelperWrapper = burnInHelperWrapper,
+                longPressViewModel =
+                    KeyguardLongPressViewModel(
+                        interactor = keyguardLongPressInteractor,
+                    ),
+                settingsMenuViewModel =
+                    KeyguardSettingsMenuViewModel(
+                        interactor = keyguardLongPressInteractor,
+                    ),
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 543875d..1e465c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -24,6 +24,7 @@
 import android.content.Intent
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
+import android.content.res.Configuration
 import android.graphics.Bitmap
 import android.graphics.Canvas
 import android.graphics.Color
@@ -42,6 +43,7 @@
 import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.util.TypedValue
 import android.view.View
 import android.view.ViewGroup
 import android.view.animation.Interpolator
@@ -2199,7 +2201,7 @@
     }
 
     @Test
-    fun bindRecommendation_carouselNotFitThreeRecs() {
+    fun bindRecommendation_carouselNotFitThreeRecs_OrientationPortrait() {
         useRealConstraintSets()
         setupUpdatedRecommendationViewHolder()
         val albumArt = getColorIcon(Color.RED)
@@ -2227,16 +2229,84 @@
 
         // set the screen width less than the width of media controls.
         player.context.resources.configuration.screenWidthDp = 350
+        player.context.resources.configuration.orientation = Configuration.ORIENTATION_PORTRAIT
         player.attachRecommendation(recommendationViewHolder)
         player.bindRecommendation(data)
 
-        assertThat(player.numberOfFittedRecommendations).isEqualTo(2)
-        assertThat(expandedSet.getVisibility(coverContainer1.id)).isEqualTo(ConstraintSet.VISIBLE)
-        assertThat(collapsedSet.getVisibility(coverContainer1.id)).isEqualTo(ConstraintSet.VISIBLE)
-        assertThat(expandedSet.getVisibility(coverContainer2.id)).isEqualTo(ConstraintSet.VISIBLE)
-        assertThat(collapsedSet.getVisibility(coverContainer2.id)).isEqualTo(ConstraintSet.VISIBLE)
-        assertThat(expandedSet.getVisibility(coverContainer3.id)).isEqualTo(ConstraintSet.GONE)
-        assertThat(collapsedSet.getVisibility(coverContainer3.id)).isEqualTo(ConstraintSet.GONE)
+        val res = player.context.resources
+        val displayAvailableWidth =
+            TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 350f, res.displayMetrics).toInt()
+        val recCoverWidth: Int =
+            (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) +
+                res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2)
+        val numOfRecs = displayAvailableWidth / recCoverWidth
+
+        assertThat(player.numberOfFittedRecommendations).isEqualTo(numOfRecs)
+        recommendationViewHolder.mediaCoverContainers.forEachIndexed { index, container ->
+            if (index < numOfRecs) {
+                assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.VISIBLE)
+                assertThat(collapsedSet.getVisibility(container.id))
+                    .isEqualTo(ConstraintSet.VISIBLE)
+            } else {
+                assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
+                assertThat(collapsedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
+            }
+        }
+    }
+
+    @Test
+    fun bindRecommendation_carouselNotFitThreeRecs_OrientationLandscape() {
+        useRealConstraintSets()
+        setupUpdatedRecommendationViewHolder()
+        val albumArt = getColorIcon(Color.RED)
+        val data =
+            smartspaceData.copy(
+                recommendations =
+                    listOf(
+                        SmartspaceAction.Builder("id1", "title1")
+                            .setSubtitle("subtitle1")
+                            .setIcon(albumArt)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id2", "title2")
+                            .setSubtitle("subtitle1")
+                            .setIcon(albumArt)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id3", "title3")
+                            .setSubtitle("subtitle1")
+                            .setIcon(albumArt)
+                            .setExtras(Bundle.EMPTY)
+                            .build()
+                    )
+            )
+
+        // set the screen width less than the width of media controls.
+        // We should have dp width less than 378 to test. In landscape we should have 2x.
+        player.context.resources.configuration.screenWidthDp = 700
+        player.context.resources.configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+        player.attachRecommendation(recommendationViewHolder)
+        player.bindRecommendation(data)
+
+        val res = player.context.resources
+        val displayAvailableWidth =
+            TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 350f, res.displayMetrics).toInt()
+        val recCoverWidth: Int =
+            (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) +
+                res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2)
+        val numOfRecs = displayAvailableWidth / recCoverWidth
+
+        assertThat(player.numberOfFittedRecommendations).isEqualTo(numOfRecs)
+        recommendationViewHolder.mediaCoverContainers.forEachIndexed { index, container ->
+            if (index < numOfRecs) {
+                assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.VISIBLE)
+                assertThat(collapsedSet.getVisibility(container.id))
+                    .isEqualTo(ConstraintSet.VISIBLE)
+            } else {
+                assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
+                assertThat(collapsedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
+            }
+        }
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
index c96853d..a0c376f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.notetask.NoteTaskController
 import com.android.systemui.notetask.NoteTaskEntryPoint
 import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import org.junit.After
@@ -38,6 +39,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.never
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -86,15 +88,28 @@
 
     @Test
     fun startActivityOnWorkProfileUser_shouldLaunchProxyActivity() {
+        val mainUserHandle: UserHandle = mainUser.userHandle
         userTracker.set(listOf(mainUser, workProfileUser), selectedUserIndex = 1)
         whenever(userManager.isManagedProfile).thenReturn(true)
+        whenever(userManager.mainUser).thenReturn(mainUserHandle)
 
         activityRule.launchActivity(/* startIntent= */ null)
 
-        val mainUserHandle: UserHandle = mainUser.userHandle
         verify(noteTaskController).startNoteTaskProxyActivityForUser(eq(mainUserHandle))
     }
 
+    @Test
+    fun startActivityOnWorkProfileUser_noMainUser_shouldNotLaunch() {
+        userTracker.set(listOf(mainUser, workProfileUser), selectedUserIndex = 1)
+        whenever(userManager.isManagedProfile).thenReturn(true)
+        whenever(userManager.mainUser).thenReturn(null)
+
+        activityRule.launchActivity(/* startIntent= */ null)
+
+        verify(noteTaskController, never()).showNoteTask(any())
+        verify(noteTaskController, never()).startNoteTaskProxyActivityForUser(any())
+    }
+
     private companion object {
         val mainUser = UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
         val workProfileUser =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 34d2b14..aa92177 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -51,8 +51,10 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.nano.SystemUIProtoDump;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.qs.QSFactory;
@@ -62,7 +64,6 @@
 import com.android.systemui.qs.external.CustomTileStatePersister;
 import com.android.systemui.qs.external.TileLifecycleManager;
 import com.android.systemui.qs.external.TileServiceKey;
-import com.android.systemui.qs.external.TileServiceRequestController;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserFileManager;
@@ -110,8 +111,6 @@
     @Mock
     private Provider<AutoTileManager> mAutoTiles;
     @Mock
-    private DumpManager mDumpManager;
-    @Mock
     private CentralSurfaces mCentralSurfaces;
     @Mock
     private QSLogger mQSLogger;
@@ -125,10 +124,6 @@
     @Mock
     private CustomTileStatePersister mCustomTileStatePersister;
     @Mock
-    private TileServiceRequestController.Builder mTileServiceRequestControllerBuilder;
-    @Mock
-    private TileServiceRequestController mTileServiceRequestController;
-    @Mock
     private TileLifecycleManager.Factory mTileLifecycleManagerFactory;
     @Mock
     private TileLifecycleManager mTileLifecycleManager;
@@ -137,6 +132,8 @@
 
     private SparseArray<SharedPreferences> mSharedPreferencesByUser;
 
+    private FakeFeatureFlags mFeatureFlags;
+
     private FakeExecutor mMainExecutor;
 
     private QSTileHost mQSTileHost;
@@ -144,12 +141,13 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mFeatureFlags = new FakeFeatureFlags();
+
+        mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, false);
+
         mMainExecutor = new FakeExecutor(new FakeSystemClock());
 
         mSharedPreferencesByUser = new SparseArray<>();
-
-        when(mTileServiceRequestControllerBuilder.create(any()))
-                .thenReturn(mTileServiceRequestController);
         when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class)))
                 .thenReturn(mTileLifecycleManager);
         when(mUserFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
@@ -165,10 +163,9 @@
         mSecureSettings = new FakeSettings();
         saveSetting("");
         mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor,
-                mPluginManager, mTunerService, mAutoTiles, mDumpManager, mCentralSurfaces,
+                mPluginManager, mTunerService, mAutoTiles, mCentralSurfaces,
                 mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
-                mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory,
-                mUserFileManager);
+                mTileLifecycleManagerFactory, mUserFileManager, mFeatureFlags);
 
         mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
             @Override
@@ -686,18 +683,16 @@
         TestQSTileHost(Context context,
                 QSFactory defaultFactory, Executor mainExecutor,
                 PluginManager pluginManager, TunerService tunerService,
-                Provider<AutoTileManager> autoTiles, DumpManager dumpManager,
+                Provider<AutoTileManager> autoTiles,
                 CentralSurfaces centralSurfaces, QSLogger qsLogger, UiEventLogger uiEventLogger,
                 UserTracker userTracker, SecureSettings secureSettings,
                 CustomTileStatePersister customTileStatePersister,
-                TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
                 TileLifecycleManager.Factory tileLifecycleManagerFactory,
-                UserFileManager userFileManager) {
+                UserFileManager userFileManager, FeatureFlags featureFlags) {
             super(context, defaultFactory, mainExecutor, pluginManager,
-                    tunerService, autoTiles, dumpManager, Optional.of(centralSurfaces), qsLogger,
+                    tunerService, autoTiles,  Optional.of(centralSurfaces), qsLogger,
                     uiEventLogger, userTracker, secureSettings, customTileStatePersister,
-                    tileServiceRequestControllerBuilder, tileLifecycleManagerFactory,
-                    userFileManager);
+                    tileLifecycleManagerFactory, userFileManager, featureFlags);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index c03849b..50a8d26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -170,6 +170,21 @@
         }
 
     @Test
+    fun addTileAtPosition_tooLarge_addedAtEnd() =
+        testScope.runTest {
+            val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+            val specs = "a,custom(b/c)"
+            storeTilesForUser(specs, 0)
+
+            underTest.addTile(userId = 0, TileSpec.create("d"), position = 100)
+
+            val expected = "a,custom(b/c),d"
+            assertThat(loadTilesForUser(0)).isEqualTo(expected)
+            assertThat(tiles).isEqualTo(expected.toTileSpecs())
+        }
+
+    @Test
     fun addTileForOtherUser_addedInThatUser() =
         testScope.runTest {
             val tilesUser0 by collectLastValue(underTest.tilesSpecs(0))
@@ -187,27 +202,27 @@
         }
 
     @Test
-    fun removeTile() =
+    fun removeTiles() =
         testScope.runTest {
             val tiles by collectLastValue(underTest.tilesSpecs(0))
 
             storeTilesForUser("a,b", 0)
 
-            underTest.removeTile(userId = 0, TileSpec.create("a"))
+            underTest.removeTiles(userId = 0, listOf(TileSpec.create("a")))
 
             assertThat(loadTilesForUser(0)).isEqualTo("b")
             assertThat(tiles).isEqualTo("b".toTileSpecs())
         }
 
     @Test
-    fun removeTileNotThere_noop() =
+    fun removeTilesNotThere_noop() =
         testScope.runTest {
             val tiles by collectLastValue(underTest.tilesSpecs(0))
 
             val specs = "a,b"
             storeTilesForUser(specs, 0)
 
-            underTest.removeTile(userId = 0, TileSpec.create("c"))
+            underTest.removeTiles(userId = 0, listOf(TileSpec.create("c")))
 
             assertThat(loadTilesForUser(0)).isEqualTo(specs)
             assertThat(tiles).isEqualTo(specs.toTileSpecs())
@@ -221,7 +236,7 @@
             val specs = "a,b"
             storeTilesForUser(specs, 0)
 
-            underTest.removeTile(userId = 0, TileSpec.Invalid)
+            underTest.removeTiles(userId = 0, listOf(TileSpec.Invalid))
 
             assertThat(loadTilesForUser(0)).isEqualTo(specs)
             assertThat(tiles).isEqualTo(specs.toTileSpecs())
@@ -237,7 +252,7 @@
             storeTilesForUser(specs, 0)
             storeTilesForUser(specs, 1)
 
-            underTest.removeTile(userId = 1, TileSpec.create("a"))
+            underTest.removeTiles(userId = 1, listOf(TileSpec.create("a")))
 
             assertThat(loadTilesForUser(0)).isEqualTo(specs)
             assertThat(user0Tiles).isEqualTo(specs.toTileSpecs())
@@ -246,6 +261,19 @@
         }
 
     @Test
+    fun removeMultipleTiles() =
+        testScope.runTest {
+            val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+            storeTilesForUser("a,b,c,d", 0)
+
+            underTest.removeTiles(userId = 0, listOf(TileSpec.create("a"), TileSpec.create("c")))
+
+            assertThat(loadTilesForUser(0)).isEqualTo("b,d")
+            assertThat(tiles).isEqualTo("b,d".toTileSpecs())
+        }
+
+    @Test
     fun changeTiles() =
         testScope.runTest {
             val tiles by collectLastValue(underTest.tilesSpecs(0))
@@ -310,8 +338,8 @@
             storeTilesForUser(specs, 0)
 
             coroutineScope {
-                underTest.removeTile(userId = 0, TileSpec.create("c"))
-                underTest.removeTile(userId = 0, TileSpec.create("a"))
+                underTest.removeTiles(userId = 0, listOf(TileSpec.create("c")))
+                underTest.removeTiles(userId = 0, listOf(TileSpec.create("a")))
             }
 
             assertThat(loadTilesForUser(0)).isEqualTo("b")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
new file mode 100644
index 0000000..7ecb4dc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -0,0 +1,674 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dump.nano.SystemUIProtoDump
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.qs.QSTile.BooleanState
+import com.android.systemui.qs.FakeQSFactory
+import com.android.systemui.qs.external.CustomTile
+import com.android.systemui.qs.external.CustomTileStatePersister
+import com.android.systemui.qs.external.TileLifecycleManager
+import com.android.systemui.qs.external.TileServiceKey
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.FakeCustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import com.android.systemui.qs.pipeline.domain.model.TileModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.qs.toProto
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import com.google.protobuf.nano.MessageNano
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class CurrentTilesInteractorImplTest : SysuiTestCase() {
+
+    private val tileSpecRepository: TileSpecRepository = FakeTileSpecRepository()
+    private val userRepository = FakeUserRepository()
+    private val tileFactory = FakeQSFactory(::tileCreator)
+    private val customTileAddedRepository: CustomTileAddedRepository =
+        FakeCustomTileAddedRepository()
+    private val featureFlags = FakeFeatureFlags()
+    private val tileLifecycleManagerFactory = TLMFactory()
+
+    @Mock private lateinit var customTileStatePersister: CustomTileStatePersister
+
+    @Mock private lateinit var userTracker: UserTracker
+
+    @Mock private lateinit var logger: QSPipelineLogger
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private val unavailableTiles = mutableSetOf("e")
+
+    private lateinit var underTest: CurrentTilesInteractorImpl
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true)
+
+        userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
+        setUserTracker(0)
+
+        underTest =
+            CurrentTilesInteractorImpl(
+                tileSpecRepository = tileSpecRepository,
+                userRepository = userRepository,
+                customTileStatePersister = customTileStatePersister,
+                tileFactory = tileFactory,
+                customTileAddedRepository = customTileAddedRepository,
+                tileLifecycleManagerFactory = tileLifecycleManagerFactory,
+                userTracker = userTracker,
+                mainDispatcher = testDispatcher,
+                backgroundDispatcher = testDispatcher,
+                scope = testScope.backgroundScope,
+                logger = logger,
+                featureFlags = featureFlags,
+            )
+    }
+
+    @Test
+    fun initialState() =
+        testScope.runTest(USER_INFO_0) {
+            assertThat(underTest.currentTiles.value).isEmpty()
+            assertThat(underTest.currentQSTiles).isEmpty()
+            assertThat(underTest.currentTilesSpecs).isEmpty()
+            assertThat(underTest.userId.value).isEqualTo(0)
+            assertThat(underTest.userContext.value.userId).isEqualTo(0)
+        }
+
+    @Test
+    fun correctTiles() =
+        testScope.runTest(USER_INFO_0) {
+            val tiles by collectLastValue(underTest.currentTiles)
+
+            val specs =
+                listOf(
+                    TileSpec.create("a"),
+                    TileSpec.create("e"),
+                    CUSTOM_TILE_SPEC,
+                    TileSpec.create("d"),
+                    TileSpec.create("non_existent")
+                )
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+            // check each tile
+
+            // Tile a
+            val tile0 = tiles!![0]
+            assertThat(tile0.spec).isEqualTo(specs[0])
+            assertThat(tile0.tile.tileSpec).isEqualTo(specs[0].spec)
+            assertThat(tile0.tile).isInstanceOf(FakeQSTile::class.java)
+            assertThat(tile0.tile.isAvailable).isTrue()
+
+            // Tile e is not available and is not in the list
+
+            // Custom Tile
+            val tile1 = tiles!![1]
+            assertThat(tile1.spec).isEqualTo(specs[2])
+            assertThat(tile1.tile.tileSpec).isEqualTo(specs[2].spec)
+            assertThat(tile1.tile).isInstanceOf(CustomTile::class.java)
+            assertThat(tile1.tile.isAvailable).isTrue()
+
+            // Tile d
+            val tile2 = tiles!![2]
+            assertThat(tile2.spec).isEqualTo(specs[3])
+            assertThat(tile2.tile.tileSpec).isEqualTo(specs[3].spec)
+            assertThat(tile2.tile).isInstanceOf(FakeQSTile::class.java)
+            assertThat(tile2.tile.isAvailable).isTrue()
+
+            // Tile non-existent shouldn't be created. Therefore, only 3 tiles total
+            assertThat(tiles?.size).isEqualTo(3)
+        }
+
+    @Test
+    fun logTileCreated() =
+        testScope.runTest(USER_INFO_0) {
+            val specs =
+                listOf(
+                    TileSpec.create("a"),
+                    CUSTOM_TILE_SPEC,
+                )
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+            runCurrent()
+
+            specs.forEach { verify(logger).logTileCreated(it) }
+        }
+
+    @Test
+    fun logTileNotFoundInFactory() =
+        testScope.runTest(USER_INFO_0) {
+            val specs =
+                listOf(
+                    TileSpec.create("non_existing"),
+                )
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+            runCurrent()
+
+            verify(logger, never()).logTileCreated(any())
+            verify(logger).logTileNotFoundInFactory(specs[0])
+        }
+
+    @Test
+    fun tileNotAvailableDestroyed_logged() =
+        testScope.runTest(USER_INFO_0) {
+            val specs =
+                listOf(
+                    TileSpec.create("e"),
+                )
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+            runCurrent()
+
+            verify(logger, never()).logTileCreated(any())
+            verify(logger)
+                .logTileDestroyed(
+                    specs[0],
+                    QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE
+                )
+        }
+
+    @Test
+    fun someTilesNotValid_repositorySetToDefinitiveList() =
+        testScope.runTest(USER_INFO_0) {
+            val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+
+            val specs =
+                listOf(
+                    TileSpec.create("a"),
+                    TileSpec.create("e"),
+                )
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+            assertThat(tiles).isEqualTo(listOf(TileSpec.create("a")))
+        }
+
+    @Test
+    fun deduplicatedTiles() =
+        testScope.runTest(USER_INFO_0) {
+            val tiles by collectLastValue(underTest.currentTiles)
+
+            val specs = listOf(TileSpec.create("a"), TileSpec.create("a"))
+
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+            assertThat(tiles?.size).isEqualTo(1)
+            assertThat(tiles!![0].spec).isEqualTo(specs[0])
+        }
+
+    @Test
+    fun tilesChange_platformTileNotRecreated() =
+        testScope.runTest(USER_INFO_0) {
+            val tiles by collectLastValue(underTest.currentTiles)
+
+            val specs =
+                listOf(
+                    TileSpec.create("a"),
+                )
+
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+            val originalTileA = tiles!![0].tile
+
+            tileSpecRepository.addTile(USER_INFO_0.id, TileSpec.create("b"))
+
+            assertThat(tiles?.size).isEqualTo(2)
+            assertThat(tiles!![0].tile).isSameInstanceAs(originalTileA)
+        }
+
+    @Test
+    fun tileRemovedIsDestroyed() =
+        testScope.runTest(USER_INFO_0) {
+            val tiles by collectLastValue(underTest.currentTiles)
+
+            val specs = listOf(TileSpec.create("a"), TileSpec.create("c"))
+
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+            val originalTileC = tiles!![1].tile
+
+            tileSpecRepository.removeTiles(USER_INFO_0.id, listOf(TileSpec.create("c")))
+
+            assertThat(tiles?.size).isEqualTo(1)
+            assertThat(tiles!![0].spec).isEqualTo(TileSpec.create("a"))
+
+            assertThat((originalTileC as FakeQSTile).destroyed).isTrue()
+            verify(logger)
+                .logTileDestroyed(
+                    TileSpec.create("c"),
+                    QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
+                )
+        }
+
+    @Test
+    fun tileBecomesNotAvailable_destroyed() =
+        testScope.runTest(USER_INFO_0) {
+            val tiles by collectLastValue(underTest.currentTiles)
+            val repoTiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+
+            val specs = listOf(TileSpec.create("a"))
+
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+            val originalTileA = tiles!![0].tile
+
+            // Tile becomes unavailable
+            (originalTileA as FakeQSTile).available = false
+            unavailableTiles.add("a")
+            // and there is some change in the specs
+            tileSpecRepository.addTile(USER_INFO_0.id, TileSpec.create("b"))
+            runCurrent()
+
+            assertThat(originalTileA.destroyed).isTrue()
+            verify(logger)
+                .logTileDestroyed(
+                    TileSpec.create("a"),
+                    QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE
+                )
+
+            assertThat(tiles?.size).isEqualTo(1)
+            assertThat(tiles!![0].spec).isEqualTo(TileSpec.create("b"))
+            assertThat(tiles!![0].tile).isNotSameInstanceAs(originalTileA)
+
+            assertThat(repoTiles).isEqualTo(tiles!!.map(TileModel::spec))
+        }
+
+    @Test
+    fun userChange_tilesChange() =
+        testScope.runTest(USER_INFO_0) {
+            val tiles by collectLastValue(underTest.currentTiles)
+
+            val specs0 = listOf(TileSpec.create("a"))
+            val specs1 = listOf(TileSpec.create("b"))
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs0)
+            tileSpecRepository.setTiles(USER_INFO_1.id, specs1)
+
+            switchUser(USER_INFO_1)
+
+            assertThat(tiles!![0].spec).isEqualTo(specs1[0])
+            assertThat(tiles!![0].tile.tileSpec).isEqualTo(specs1[0].spec)
+        }
+
+    @Test
+    fun tileNotPresentInSecondaryUser_destroyedInUserChange() =
+        testScope.runTest(USER_INFO_0) {
+            val tiles by collectLastValue(underTest.currentTiles)
+
+            val specs0 = listOf(TileSpec.create("a"))
+            val specs1 = listOf(TileSpec.create("b"))
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs0)
+            tileSpecRepository.setTiles(USER_INFO_1.id, specs1)
+
+            val originalTileA = tiles!![0].tile
+
+            switchUser(USER_INFO_1)
+            runCurrent()
+
+            assertThat((originalTileA as FakeQSTile).destroyed).isTrue()
+            verify(logger)
+                .logTileDestroyed(
+                    specs0[0],
+                    QSPipelineLogger.TileDestroyedReason.TILE_NOT_PRESENT_IN_NEW_USER
+                )
+        }
+
+    @Test
+    fun userChange_customTileDestroyed_lifecycleNotTerminated() {
+        testScope.runTest(USER_INFO_0) {
+            val tiles by collectLastValue(underTest.currentTiles)
+
+            val specs = listOf(CUSTOM_TILE_SPEC)
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+            tileSpecRepository.setTiles(USER_INFO_1.id, specs)
+
+            val originalCustomTile = tiles!![0].tile
+
+            switchUser(USER_INFO_1)
+            runCurrent()
+
+            verify(originalCustomTile).destroy()
+            assertThat(tileLifecycleManagerFactory.created).isEmpty()
+        }
+    }
+
+    @Test
+    fun userChange_sameTileUserChanged() =
+        testScope.runTest(USER_INFO_0) {
+            val tiles by collectLastValue(underTest.currentTiles)
+
+            val specs = listOf(TileSpec.create("a"))
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+            tileSpecRepository.setTiles(USER_INFO_1.id, specs)
+
+            val originalTileA = tiles!![0].tile as FakeQSTile
+            assertThat(originalTileA.user).isEqualTo(USER_INFO_0.id)
+
+            switchUser(USER_INFO_1)
+            runCurrent()
+
+            assertThat(tiles!![0].tile).isSameInstanceAs(originalTileA)
+            assertThat(originalTileA.user).isEqualTo(USER_INFO_1.id)
+            verify(logger).logTileUserChanged(specs[0], USER_INFO_1.id)
+        }
+
+    @Test
+    fun addTile() =
+        testScope.runTest(USER_INFO_0) {
+            val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+            val spec = TileSpec.create("a")
+            val currentSpecs =
+                listOf(
+                    TileSpec.create("b"),
+                    TileSpec.create("c"),
+                )
+            tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+
+            underTest.addTile(spec, position = 1)
+
+            val expectedSpecs =
+                listOf(
+                    TileSpec.create("b"),
+                    spec,
+                    TileSpec.create("c"),
+                )
+            assertThat(tiles).isEqualTo(expectedSpecs)
+        }
+
+    @Test
+    fun addTile_currentUser() =
+        testScope.runTest(USER_INFO_1) {
+            val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+            val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id))
+            val spec = TileSpec.create("a")
+            val currentSpecs =
+                listOf(
+                    TileSpec.create("b"),
+                    TileSpec.create("c"),
+                )
+            tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+            tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs)
+
+            switchUser(USER_INFO_1)
+            underTest.addTile(spec, position = 1)
+
+            assertThat(tiles0).isEqualTo(currentSpecs)
+
+            val expectedSpecs =
+                listOf(
+                    TileSpec.create("b"),
+                    spec,
+                    TileSpec.create("c"),
+                )
+            assertThat(tiles1).isEqualTo(expectedSpecs)
+        }
+
+    @Test
+    fun removeTile_platform() =
+        testScope.runTest(USER_INFO_0) {
+            val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+
+            val specs = listOf(TileSpec.create("a"), TileSpec.create("b"))
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+            runCurrent()
+
+            underTest.removeTiles(specs.subList(0, 1))
+
+            assertThat(tiles).isEqualTo(specs.subList(1, 2))
+        }
+
+    @Test
+    fun removeTile_customTile_lifecycleEnded() {
+        testScope.runTest(USER_INFO_0) {
+            val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+
+            val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+            runCurrent()
+            assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id))
+                .isTrue()
+
+            underTest.removeTiles(listOf(CUSTOM_TILE_SPEC))
+
+            assertThat(tiles).isEqualTo(specs.subList(0, 1))
+
+            val tileLifecycleManager =
+                tileLifecycleManagerFactory.created[USER_INFO_0.id to TEST_COMPONENT]
+            assertThat(tileLifecycleManager).isNotNull()
+
+            with(inOrder(tileLifecycleManager!!)) {
+                verify(tileLifecycleManager).onStopListening()
+                verify(tileLifecycleManager).onTileRemoved()
+                verify(tileLifecycleManager).flushMessagesAndUnbind()
+            }
+            assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id))
+                .isFalse()
+            verify(customTileStatePersister)
+                .removeState(TileServiceKey(TEST_COMPONENT, USER_INFO_0.id))
+        }
+    }
+
+    @Test
+    fun removeTiles_currentUser() =
+        testScope.runTest {
+            val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+            val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id))
+            val currentSpecs =
+                listOf(
+                    TileSpec.create("a"),
+                    TileSpec.create("b"),
+                    TileSpec.create("c"),
+                )
+            tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+            tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs)
+
+            switchUser(USER_INFO_1)
+            runCurrent()
+
+            underTest.removeTiles(currentSpecs.subList(0, 2))
+
+            assertThat(tiles0).isEqualTo(currentSpecs)
+            assertThat(tiles1).isEqualTo(currentSpecs.subList(2, 3))
+        }
+
+    @Test
+    fun setTiles() =
+        testScope.runTest(USER_INFO_0) {
+            val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+
+            val currentSpecs = listOf(TileSpec.create("a"), TileSpec.create("b"))
+            tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+            runCurrent()
+
+            val newSpecs = listOf(TileSpec.create("b"), TileSpec.create("c"), TileSpec.create("a"))
+            underTest.setTiles(newSpecs)
+            runCurrent()
+
+            assertThat(tiles).isEqualTo(newSpecs)
+        }
+
+    @Test
+    fun setTiles_customTiles_lifecycleEndedIfGone() =
+        testScope.runTest(USER_INFO_0) {
+            val otherCustomTileSpec = TileSpec.create("custom(b/c)")
+
+            val currentSpecs = listOf(CUSTOM_TILE_SPEC, TileSpec.create("a"), otherCustomTileSpec)
+            tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+            runCurrent()
+
+            val newSpecs =
+                listOf(
+                    otherCustomTileSpec,
+                    TileSpec.create("a"),
+                )
+
+            underTest.setTiles(newSpecs)
+            runCurrent()
+
+            val tileLifecycleManager =
+                tileLifecycleManagerFactory.created[USER_INFO_0.id to TEST_COMPONENT]!!
+
+            with(inOrder(tileLifecycleManager)) {
+                verify(tileLifecycleManager).onStopListening()
+                verify(tileLifecycleManager).onTileRemoved()
+                verify(tileLifecycleManager).flushMessagesAndUnbind()
+            }
+            assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id))
+                .isFalse()
+            verify(customTileStatePersister)
+                .removeState(TileServiceKey(TEST_COMPONENT, USER_INFO_0.id))
+        }
+
+    @Test
+    fun protoDump() =
+        testScope.runTest(USER_INFO_0) {
+            val tiles by collectLastValue(underTest.currentTiles)
+            val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
+
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+            val stateA = tiles!![0].tile.state
+            stateA.fillIn(Tile.STATE_INACTIVE, "A", "AA")
+            val stateCustom = QSTile.BooleanState()
+            stateCustom.fillIn(Tile.STATE_ACTIVE, "B", "BB")
+            stateCustom.spec = CUSTOM_TILE_SPEC.spec
+            whenever(tiles!![1].tile.state).thenReturn(stateCustom)
+
+            val proto = SystemUIProtoDump()
+            underTest.dumpProto(proto, emptyArray())
+
+            assertThat(MessageNano.messageNanoEquals(proto.tiles[0], stateA.toProto())).isTrue()
+            assertThat(MessageNano.messageNanoEquals(proto.tiles[1], stateCustom.toProto()))
+                .isTrue()
+        }
+
+    private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) {
+        this.state = state
+        this.label = label
+        this.secondaryLabel = secondaryLabel
+        if (this is BooleanState) {
+            value = state == Tile.STATE_ACTIVE
+        }
+    }
+
+    private fun tileCreator(spec: String): QSTile? {
+        val currentUser = userTracker.userId
+        return when (spec) {
+            CUSTOM_TILE_SPEC.spec ->
+                mock<CustomTile> {
+                    var tileSpecReference: String? = null
+                    whenever(user).thenReturn(currentUser)
+                    whenever(component).thenReturn(CUSTOM_TILE_SPEC.componentName)
+                    whenever(isAvailable).thenReturn(true)
+                    whenever(setTileSpec(anyString())).thenAnswer {
+                        tileSpecReference = it.arguments[0] as? String
+                        Unit
+                    }
+                    whenever(tileSpec).thenAnswer { tileSpecReference }
+                    // Also, add it to the set of added tiles (as this happens as part of the tile
+                    // creation).
+                    customTileAddedRepository.setTileAdded(
+                        CUSTOM_TILE_SPEC.componentName,
+                        currentUser,
+                        true
+                    )
+                }
+            in VALID_TILES -> FakeQSTile(currentUser, available = spec !in unavailableTiles)
+            else -> null
+        }
+    }
+
+    private fun TestScope.runTest(user: UserInfo, body: suspend TestScope.() -> Unit) {
+        return runTest {
+            switchUser(user)
+            body()
+        }
+    }
+
+    private suspend fun switchUser(user: UserInfo) {
+        setUserTracker(user.id)
+        userRepository.setSelectedUserInfo(user)
+    }
+
+    private fun setUserTracker(user: Int) {
+        val mockContext = mockUserContext(user)
+        whenever(userTracker.userContext).thenReturn(mockContext)
+        whenever(userTracker.userId).thenReturn(user)
+    }
+
+    private class TLMFactory : TileLifecycleManager.Factory {
+
+        val created = mutableMapOf<Pair<Int, ComponentName>, TileLifecycleManager>()
+
+        override fun create(intent: Intent, userHandle: UserHandle): TileLifecycleManager {
+            val componentName = intent.component!!
+            val user = userHandle.identifier
+            val manager: TileLifecycleManager = mock()
+            created[user to componentName] = manager
+            return manager
+        }
+    }
+
+    private fun mockUserContext(user: Int): Context {
+        return mock {
+            whenever(this.userId).thenReturn(user)
+            whenever(this.user).thenReturn(UserHandle.of(user))
+        }
+    }
+
+    companion object {
+        private val USER_INFO_0 = UserInfo().apply { id = 0 }
+        private val USER_INFO_1 = UserInfo().apply { id = 1 }
+
+        private val VALID_TILES = setOf("a", "b", "c", "d", "e")
+        private val TEST_COMPONENT = ComponentName("pkg", "cls")
+        private val CUSTOM_TILE_SPEC = TileSpec.Companion.create(TEST_COMPONENT)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt
new file mode 100644
index 0000000..e509696
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import android.content.Context
+import android.view.View
+import com.android.internal.logging.InstanceId
+import com.android.systemui.plugins.qs.QSIconView
+import com.android.systemui.plugins.qs.QSTile
+
+class FakeQSTile(
+    var user: Int,
+    var available: Boolean = true,
+) : QSTile {
+    private var tileSpec: String? = null
+    var destroyed = false
+    private val state = QSTile.State()
+
+    override fun getTileSpec(): String? {
+        return tileSpec
+    }
+
+    override fun isAvailable(): Boolean {
+        return available
+    }
+
+    override fun setTileSpec(tileSpec: String?) {
+        this.tileSpec = tileSpec
+        state.spec = tileSpec
+    }
+
+    override fun refreshState() {}
+
+    override fun addCallback(callback: QSTile.Callback?) {}
+
+    override fun removeCallback(callback: QSTile.Callback?) {}
+
+    override fun removeCallbacks() {}
+
+    override fun createTileView(context: Context?): QSIconView? {
+        return null
+    }
+
+    override fun click(view: View?) {}
+
+    override fun secondaryClick(view: View?) {}
+
+    override fun longClick(view: View?) {}
+
+    override fun userSwitch(currentUser: Int) {
+        user = currentUser
+    }
+
+    override fun getMetricsCategory(): Int {
+        return 0
+    }
+
+    override fun setListening(client: Any?, listening: Boolean) {}
+
+    override fun setDetailListening(show: Boolean) {}
+
+    override fun destroy() {
+        destroyed = true
+    }
+
+    override fun getTileLabel(): CharSequence {
+        return ""
+    }
+
+    override fun getState(): QSTile.State {
+        return state
+    }
+
+    override fun getInstanceId(): InstanceId {
+        return InstanceId.fakeInstanceId(0)
+    }
+
+    override fun isListening(): Boolean {
+        return false
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
index b55fe36..67b1099 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
@@ -20,8 +20,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.admin.DevicePolicyManager;
@@ -29,6 +31,8 @@
 import android.content.Intent;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -42,6 +46,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.Optional;
@@ -58,6 +63,9 @@
     @Mock private Optional<Bubbles> mOptionalBubbles;
     @Mock private Bubbles mBubbles;
     @Mock private DevicePolicyManager mDevicePolicyManager;
+    @Mock private UserManager mUserManager;
+
+    private AppClipsService mAppClipsService;
 
     @Before
     public void setUp() {
@@ -119,26 +127,53 @@
 
     @Test
     public void allPrerequisitesSatisfy_shouldReturnTrue() throws RemoteException {
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
-        when(mOptionalBubbles.isEmpty()).thenReturn(false);
-        when(mOptionalBubbles.get()).thenReturn(mBubbles);
-        when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
-        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
+        mockToSatisfyAllPrerequisites();
 
         assertThat(getInterfaceWithRealContext()
                 .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isTrue();
     }
 
+    @Test
+    public void isManagedProfile_shouldUseProxyConnection() throws RemoteException {
+        when(mUserManager.isManagedProfile()).thenReturn(true);
+        when(mUserManager.getMainUser()).thenReturn(UserHandle.SYSTEM);
+        IAppClipsService service = getInterfaceWithRealContext();
+        mAppClipsService.mProxyConnectorToMainProfile =
+                Mockito.spy(mAppClipsService.mProxyConnectorToMainProfile);
+
+        service.canLaunchCaptureContentActivityForNote(FAKE_TASK_ID);
+
+        verify(mAppClipsService.mProxyConnectorToMainProfile).postForResult(any());
+    }
+
+    @Test
+    public void isManagedProfile_noMainUser_shouldReturnFalse() {
+        when(mUserManager.isManagedProfile()).thenReturn(true);
+        when(mUserManager.getMainUser()).thenReturn(null);
+
+        getInterfaceWithRealContext();
+
+        assertThat(mAppClipsService.mProxyConnectorToMainProfile).isNull();
+    }
+
+    private void mockToSatisfyAllPrerequisites() {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+        when(mOptionalBubbles.isEmpty()).thenReturn(false);
+        when(mOptionalBubbles.get()).thenReturn(mBubbles);
+        when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
+        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
+    }
+
     private IAppClipsService getInterfaceWithRealContext() {
-        AppClipsService appClipsService = new AppClipsService(getContext(), mFeatureFlags,
-                mOptionalBubbles, mDevicePolicyManager);
-        return getInterfaceFromService(appClipsService);
+        mAppClipsService = new AppClipsService(getContext(), mFeatureFlags,
+                mOptionalBubbles, mDevicePolicyManager, mUserManager);
+        return getInterfaceFromService(mAppClipsService);
     }
 
     private IAppClipsService getInterfaceWithMockContext() {
-        AppClipsService appClipsService = new AppClipsService(mMockContext, mFeatureFlags,
-                mOptionalBubbles, mDevicePolicyManager);
-        return getInterfaceFromService(appClipsService);
+        mAppClipsService = new AppClipsService(mMockContext, mFeatureFlags,
+                mOptionalBubbles, mDevicePolicyManager, mUserManager);
+        return getInterfaceFromService(mAppClipsService);
     }
 
     private static IAppClipsService getInterfaceFromService(AppClipsService appClipsService) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
index ad06dcc..31a33d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
@@ -49,6 +49,8 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.rule.ActivityTestRule;
@@ -98,6 +100,9 @@
     private UserTracker mUserTracker;
     @Mock
     private UiEventLogger mUiEventLogger;
+    @Mock
+    private UserManager mUserManager;
+
     @Main
     private Handler mMainHandler;
 
@@ -109,7 +114,7 @@
                 protected AppClipsTrampolineActivityTestable create(Intent unUsed) {
                     return new AppClipsTrampolineActivityTestable(mDevicePolicyManager,
                             mFeatureFlags, mOptionalBubbles, mNoteTaskController, mPackageManager,
-                            mUserTracker, mUiEventLogger, mMainHandler);
+                            mUserTracker, mUiEventLogger, mUserManager, mMainHandler);
                 }
             };
 
@@ -264,6 +269,40 @@
         verify(mUiEventLogger).log(SCREENSHOT_FOR_NOTE_TRIGGERED, TEST_UID, TEST_CALLING_PACKAGE);
     }
 
+    @Test
+    public void startAppClipsActivity_throughWPUser_shouldStartMainUserActivity()
+            throws NameNotFoundException {
+        when(mUserManager.isManagedProfile()).thenReturn(true);
+        when(mUserManager.getMainUser()).thenReturn(UserHandle.SYSTEM);
+        mockToSatisfyAllPrerequisites();
+
+        AppClipsTrampolineActivityTestable activity = mActivityRule.launchActivity(mActivityIntent);
+        waitForIdleSync();
+
+        Intent actualIntent = activity.mStartedIntent;
+        assertThat(actualIntent.getComponent()).isEqualTo(
+                new ComponentName(mContext, AppClipsTrampolineActivity.class));
+        assertThat(actualIntent.getFlags()).isEqualTo(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+        assertThat(activity.mStartingUser).isEqualTo(UserHandle.SYSTEM);
+    }
+
+    @Test
+    public void startAppClipsActivity_throughWPUser_noMainUser_shouldFinishWithFailed()
+            throws NameNotFoundException {
+        when(mUserManager.isManagedProfile()).thenReturn(true);
+        when(mUserManager.getMainUser()).thenReturn(null);
+
+        mockToSatisfyAllPrerequisites();
+
+        mActivityRule.launchActivity(mActivityIntent);
+        waitForIdleSync();
+
+        ActivityResult actualResult = mActivityRule.getActivityResult();
+        assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
+        assertThat(getStatusCodeExtra(actualResult.getResultData()))
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+    }
+
     private void mockToSatisfyAllPrerequisites() throws NameNotFoundException {
         when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
         when(mOptionalBubbles.isEmpty()).thenReturn(false);
@@ -282,6 +321,9 @@
     public static final class AppClipsTrampolineActivityTestable extends
             AppClipsTrampolineActivity {
 
+        Intent mStartedIntent;
+        UserHandle mStartingUser;
+
         public AppClipsTrampolineActivityTestable(DevicePolicyManager devicePolicyManager,
                 FeatureFlags flags,
                 Optional<Bubbles> optionalBubbles,
@@ -289,9 +331,10 @@
                 PackageManager packageManager,
                 UserTracker userTracker,
                 UiEventLogger uiEventLogger,
+                UserManager userManager,
                 @Main Handler mainHandler) {
             super(devicePolicyManager, flags, optionalBubbles, noteTaskController, packageManager,
-                    userTracker, uiEventLogger, mainHandler);
+                    userTracker, uiEventLogger, userManager, mainHandler);
         }
 
         @Override
@@ -303,6 +346,12 @@
         public void startActivity(Intent unUsed) {
             // Ignore this intent to avoid App Clips screenshot editing activity from starting.
         }
+
+        @Override
+        public void startActivityAsUser(Intent startedIntent, UserHandle startingUser) {
+            mStartedIntent = startedIntent;
+            mStartingUser = startingUser;
+        }
     }
 
     private static int getStatusCodeExtra(Intent intent) {
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 7b37ea0..5ca3771 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -32,6 +32,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -66,6 +67,7 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.KeyguardClockSwitch;
 import com.android.keyguard.KeyguardClockSwitchController;
+import com.android.keyguard.KeyguardSliceViewController;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardStatusViewController;
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -74,6 +76,7 @@
 import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
+import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
@@ -106,6 +109,7 @@
 import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.QSFragment;
@@ -232,7 +236,6 @@
     @Mock protected KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
     @Mock protected KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent;
     @Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController;
-    @Mock protected KeyguardStatusViewController mKeyguardStatusViewController;
     @Mock protected KeyguardStatusBarViewController mKeyguardStatusBarViewController;
     @Mock protected NotificationStackScrollLayoutController
             mNotificationStackScrollLayoutController;
@@ -292,9 +295,13 @@
     @Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor;
     @Mock protected MotionEvent mDownMotionEvent;
     @Mock protected CoroutineDispatcher mMainDispatcher;
+    @Mock protected KeyguardSliceViewController mKeyguardSliceViewController;
+    @Mock protected KeyguardLogger mKeyguardLogger;
+    @Mock protected KeyguardStatusView mKeyguardStatusView;
     @Captor
     protected ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener>
             mEmptySpaceClickListenerCaptor;
+    @Mock protected ActivityStarter mActivityStarter;
 
     protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
     protected KeyguardInteractor mKeyguardInteractor;
@@ -307,6 +314,7 @@
     protected List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners;
     protected Handler mMainHandler;
     protected View.OnLayoutChangeListener mLayoutChangeListener;
+    protected KeyguardStatusViewController mKeyguardStatusViewController;
 
     protected final FalsingManagerFake mFalsingManager = new FalsingManagerFake();
     protected final Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
@@ -333,6 +341,18 @@
 
         KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
         keyguardStatusView.setId(R.id.keyguard_status_view);
+        mKeyguardStatusViewController = spy(new KeyguardStatusViewController(
+                mKeyguardStatusView,
+                mKeyguardSliceViewController,
+                mKeyguardClockSwitchController,
+                mKeyguardStateController,
+                mUpdateMonitor,
+                mConfigurationController,
+                mDozeParameters,
+                mScreenOffAnimationController,
+                mKeyguardLogger,
+                mFeatureFlags,
+                mInteractionJankMonitor));
 
         when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
         when(mHeadsUpCallback.getContext()).thenReturn(mContext);
@@ -364,12 +384,15 @@
         when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea);
         when(mKeyguardBottomArea.animate()).thenReturn(mViewPropertyAnimator);
         when(mView.animate()).thenReturn(mViewPropertyAnimator);
+        when(mKeyguardStatusView.animate()).thenReturn(mViewPropertyAnimator);
         when(mViewPropertyAnimator.translationX(anyFloat())).thenReturn(mViewPropertyAnimator);
         when(mViewPropertyAnimator.alpha(anyFloat())).thenReturn(mViewPropertyAnimator);
         when(mViewPropertyAnimator.setDuration(anyLong())).thenReturn(mViewPropertyAnimator);
+        when(mViewPropertyAnimator.setStartDelay(anyLong())).thenReturn(mViewPropertyAnimator);
         when(mViewPropertyAnimator.setInterpolator(any())).thenReturn(mViewPropertyAnimator);
         when(mViewPropertyAnimator.setListener(any())).thenReturn(mViewPropertyAnimator);
         when(mViewPropertyAnimator.setUpdateListener(any())).thenReturn(mViewPropertyAnimator);
+        when(mViewPropertyAnimator.withEndAction(any())).thenReturn(mViewPropertyAnimator);
         when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
         when(mView.findViewById(R.id.keyguard_status_view))
                 .thenReturn(mock(KeyguardStatusView.class));
@@ -575,7 +598,8 @@
                 () -> mMultiShadeInteractor,
                 mDumpManager,
                 mKeyuardLongPressViewModel,
-                mKeyguardInteractor);
+                mKeyguardInteractor,
+                mActivityStarter);
         mNotificationPanelViewController.initDependencies(
                 mCentralSurfaces,
                 null,
@@ -647,9 +671,13 @@
 
     @After
     public void tearDown() {
-        mNotificationPanelViewController.mBottomAreaShadeAlphaAnimator.cancel();
-        mNotificationPanelViewController.cancelHeightAnimator();
-        mMainHandler.removeCallbacksAndMessages(null);
+        if (mNotificationPanelViewController != null) {
+            mNotificationPanelViewController.mBottomAreaShadeAlphaAnimator.cancel();
+            mNotificationPanelViewController.cancelHeightAnimator();
+        }
+        if (mMainHandler != null) {
+            mMainHandler.removeCallbacksAndMessages(null);
+        }
     }
 
     protected void setBottomPadding(int stackBottom, int lockIconPadding, int indicationPadding,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index d530829..b043e97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -42,11 +42,11 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.qs.ChipVisibilityListener
 import com.android.systemui.qs.HeaderPrivacyIconsController
-import com.android.systemui.qs.carrier.QSCarrierGroup
-import com.android.systemui.qs.carrier.QSCarrierGroupController
 import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
 import com.android.systemui.shade.ShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
 import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
+import com.android.systemui.shade.carrier.ShadeCarrierGroup
+import com.android.systemui.shade.carrier.ShadeCarrierGroupController
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusIconContainer
@@ -88,11 +88,12 @@
     @Mock private lateinit var statusBarIconController: StatusBarIconController
     @Mock private lateinit var iconManagerFactory: StatusBarIconController.TintedIconManager.Factory
     @Mock private lateinit var iconManager: StatusBarIconController.TintedIconManager
-    @Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController
-    @Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
+    @Mock private lateinit var mShadeCarrierGroupController: ShadeCarrierGroupController
+    @Mock
+    private lateinit var mShadeCarrierGroupControllerBuilder: ShadeCarrierGroupController.Builder
     @Mock private lateinit var clock: Clock
     @Mock private lateinit var date: VariableDateView
-    @Mock private lateinit var carrierGroup: QSCarrierGroup
+    @Mock private lateinit var carrierGroup: ShadeCarrierGroup
     @Mock private lateinit var batteryMeterView: BatteryMeterView
     @Mock private lateinit var batteryMeterViewController: BatteryMeterViewController
     @Mock private lateinit var privacyIconsController: HeaderPrivacyIconsController
@@ -131,7 +132,7 @@
         whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
         whenever(date.context).thenReturn(mockedContext)
 
-        whenever<QSCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup)
+        whenever<ShadeCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup)
 
         whenever<BatteryMeterView>(view.findViewById(R.id.batteryRemainingIcon))
             .thenReturn(batteryMeterView)
@@ -142,9 +143,10 @@
         whenever(view.context).thenReturn(viewContext)
         whenever(view.resources).thenReturn(context.resources)
         whenever(statusIcons.context).thenReturn(context)
-        whenever(qsCarrierGroupControllerBuilder.setQSCarrierGroup(any()))
-            .thenReturn(qsCarrierGroupControllerBuilder)
-        whenever(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
+        whenever(mShadeCarrierGroupControllerBuilder.setShadeCarrierGroup(any()))
+            .thenReturn(mShadeCarrierGroupControllerBuilder)
+        whenever(mShadeCarrierGroupControllerBuilder.build())
+            .thenReturn(mShadeCarrierGroupController)
         whenever(view.setVisibility(anyInt())).then {
             viewVisibility = it.arguments[0] as Int
             null
@@ -175,7 +177,7 @@
                 variableDateViewControllerFactory,
                 batteryMeterViewController,
                 dumpManager,
-                qsCarrierGroupControllerBuilder,
+                mShadeCarrierGroupControllerBuilder,
                 combinedShadeHeadersConstraintManager,
                 demoModeController,
                 qsBatteryModeController,
@@ -189,7 +191,7 @@
     @Test
     fun updateListeners_registersWhenVisible() {
         makeShadeVisible()
-        verify(qsCarrierGroupController).setListening(true)
+        verify(mShadeCarrierGroupController).setListening(true)
         verify(statusBarIconController).addIconGroup(any())
     }
 
@@ -213,7 +215,7 @@
 
     @Test
     fun singleCarrier_enablesCarrierIconsInStatusIcons() {
-        whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(true)
+        whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(true)
 
         makeShadeVisible()
 
@@ -222,7 +224,7 @@
 
     @Test
     fun dualCarrier_disablesCarrierIconsInStatusIcons() {
-        whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(false)
+        whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(false)
 
         makeShadeVisible()
 
@@ -349,9 +351,9 @@
         verify(batteryMeterViewController).init()
         verify(batteryMeterViewController).ignoreTunerUpdates()
 
-        val inOrder = Mockito.inOrder(qsCarrierGroupControllerBuilder)
-        inOrder.verify(qsCarrierGroupControllerBuilder).setQSCarrierGroup(carrierGroup)
-        inOrder.verify(qsCarrierGroupControllerBuilder).build()
+        val inOrder = Mockito.inOrder(mShadeCarrierGroupControllerBuilder)
+        inOrder.verify(mShadeCarrierGroupControllerBuilder).setShadeCarrierGroup(carrierGroup)
+        inOrder.verify(mShadeCarrierGroupControllerBuilder).build()
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/CellSignalStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/CellSignalStateTest.kt
similarity index 89%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/carrier/CellSignalStateTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/shade/carrier/CellSignalStateTest.kt
index 75be74b..7a9ef62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/CellSignalStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/CellSignalStateTest.kt
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2020 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.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.carrier
+package com.android.systemui.shade.carrier
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
@@ -45,4 +45,4 @@
 
         assertNotSame(c, other)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
similarity index 84%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
index 1e7722a..2ef3d60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2020 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.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.carrier;
+package com.android.systemui.shade.carrier;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -63,13 +63,13 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
-public class QSCarrierGroupControllerTest extends LeakCheckedTest {
+public class ShadeCarrierGroupControllerTest extends LeakCheckedTest {
 
-    private QSCarrierGroupController mQSCarrierGroupController;
+    private ShadeCarrierGroupController mShadeCarrierGroupController;
     private SignalCallback mSignalCallback;
     private CarrierTextManager.CarrierTextCallback mCallback;
     @Mock
-    private QSCarrierGroup mQSCarrierGroup;
+    private ShadeCarrierGroup mShadeCarrierGroup;
     @Mock
     private ActivityStarter mActivityStarter;
     @Mock
@@ -81,14 +81,14 @@
     @Mock
     private CarrierConfigTracker mCarrierConfigTracker;
     @Mock
-    private QSCarrier mQSCarrier1;
+    private ShadeCarrier mShadeCarrier1;
     @Mock
-    private QSCarrier mQSCarrier2;
+    private ShadeCarrier mShadeCarrier2;
     @Mock
-    private QSCarrier mQSCarrier3;
+    private ShadeCarrier mShadeCarrier3;
     private TestableLooper mTestableLooper;
     @Mock
-    private QSCarrierGroupController.OnSingleCarrierChangedListener mOnSingleCarrierChangedListener;
+    private ShadeCarrierGroupController.OnSingleCarrierChangedListener mOnSingleCarrierChangedListener;
 
     private FakeSlotIndexResolver mSlotIndexResolver;
     private ClickListenerTextView mNoCarrierTextView;
@@ -116,28 +116,28 @@
                 .setListening(any(CarrierTextManager.CarrierTextCallback.class));
 
         mNoCarrierTextView = new ClickListenerTextView(mContext);
-        when(mQSCarrierGroup.getNoSimTextView()).thenReturn(mNoCarrierTextView);
-        when(mQSCarrierGroup.getCarrier1View()).thenReturn(mQSCarrier1);
-        when(mQSCarrierGroup.getCarrier2View()).thenReturn(mQSCarrier2);
-        when(mQSCarrierGroup.getCarrier3View()).thenReturn(mQSCarrier3);
-        when(mQSCarrierGroup.getCarrierDivider1()).thenReturn(new View(mContext));
-        when(mQSCarrierGroup.getCarrierDivider2()).thenReturn(new View(mContext));
+        when(mShadeCarrierGroup.getNoSimTextView()).thenReturn(mNoCarrierTextView);
+        when(mShadeCarrierGroup.getCarrier1View()).thenReturn(mShadeCarrier1);
+        when(mShadeCarrierGroup.getCarrier2View()).thenReturn(mShadeCarrier2);
+        when(mShadeCarrierGroup.getCarrier3View()).thenReturn(mShadeCarrier3);
+        when(mShadeCarrierGroup.getCarrierDivider1()).thenReturn(new View(mContext));
+        when(mShadeCarrierGroup.getCarrierDivider2()).thenReturn(new View(mContext));
 
         mSlotIndexResolver = new FakeSlotIndexResolver();
 
-        mQSCarrierGroupController = new QSCarrierGroupController.Builder(
+        mShadeCarrierGroupController = new ShadeCarrierGroupController.Builder(
                 mActivityStarter, handler, TestableLooper.get(this).getLooper(),
                 mNetworkController, mCarrierTextControllerBuilder, mContext, mCarrierConfigTracker,
                 mSlotIndexResolver)
-                .setQSCarrierGroup(mQSCarrierGroup)
+                .setShadeCarrierGroup(mShadeCarrierGroup)
                 .build();
 
-        mQSCarrierGroupController.setListening(true);
+        mShadeCarrierGroupController.setListening(true);
     }
 
     @Test
     public void testInitiallyMultiCarrier() {
-        assertFalse(mQSCarrierGroupController.isSingleCarrier());
+        assertFalse(mShadeCarrierGroupController.isSingleCarrier());
     }
 
     @Test // throws no Exception
@@ -257,12 +257,12 @@
                 true /* airplaneMode */);
         mCallback.updateCarrierInfo(info);
         mTestableLooper.processAllMessages();
-        assertEquals(View.GONE, mQSCarrierGroup.getNoSimTextView().getVisibility());
+        assertEquals(View.GONE, mShadeCarrierGroup.getNoSimTextView().getVisibility());
     }
 
     @Test
     public void testListenerNotCalledOnRegistreation() {
-        mQSCarrierGroupController
+        mShadeCarrierGroupController
                 .setOnSingleCarrierChangedListener(mOnSingleCarrierChangedListener);
 
         verify(mOnSingleCarrierChangedListener, never()).onSingleCarrierChanged(anyBoolean());
@@ -282,9 +282,9 @@
         mCallback.updateCarrierInfo(info);
         mTestableLooper.processAllMessages();
 
-        verify(mQSCarrier1).updateState(any(), eq(true));
-        verify(mQSCarrier2).updateState(any(), eq(true));
-        verify(mQSCarrier3).updateState(any(), eq(true));
+        verify(mShadeCarrier1).updateState(any(), eq(true));
+        verify(mShadeCarrier2).updateState(any(), eq(true));
+        verify(mShadeCarrier3).updateState(any(), eq(true));
     }
 
     @Test
@@ -301,9 +301,9 @@
         mCallback.updateCarrierInfo(info);
         mTestableLooper.processAllMessages();
 
-        verify(mQSCarrier1).updateState(any(), eq(false));
-        verify(mQSCarrier2).updateState(any(), eq(false));
-        verify(mQSCarrier3).updateState(any(), eq(false));
+        verify(mShadeCarrier1).updateState(any(), eq(false));
+        verify(mShadeCarrier2).updateState(any(), eq(false));
+        verify(mShadeCarrier3).updateState(any(), eq(false));
     }
 
     @Test
@@ -327,7 +327,7 @@
         mCallback.updateCarrierInfo(singleCarrierInfo);
         mTestableLooper.processAllMessages();
 
-        mQSCarrierGroupController
+        mShadeCarrierGroupController
                 .setOnSingleCarrierChangedListener(mOnSingleCarrierChangedListener);
         reset(mOnSingleCarrierChangedListener);
 
@@ -353,7 +353,7 @@
         mCallback.updateCarrierInfo(singleCarrierInfo);
         mTestableLooper.processAllMessages();
 
-        mQSCarrierGroupController
+        mShadeCarrierGroupController
                 .setOnSingleCarrierChangedListener(mOnSingleCarrierChangedListener);
 
         mCallback.updateCarrierInfo(singleCarrierInfo);
@@ -375,7 +375,7 @@
         mCallback.updateCarrierInfo(multiCarrierInfo);
         mTestableLooper.processAllMessages();
 
-        mQSCarrierGroupController
+        mShadeCarrierGroupController
                 .setOnSingleCarrierChangedListener(mOnSingleCarrierChangedListener);
 
         mCallback.updateCarrierInfo(multiCarrierInfo);
@@ -389,12 +389,12 @@
         ArgumentCaptor<View.OnClickListener> captor =
                 ArgumentCaptor.forClass(View.OnClickListener.class);
 
-        verify(mQSCarrier1).setOnClickListener(captor.capture());
-        verify(mQSCarrier2).setOnClickListener(captor.getValue());
-        verify(mQSCarrier3).setOnClickListener(captor.getValue());
+        verify(mShadeCarrier1).setOnClickListener(captor.capture());
+        verify(mShadeCarrier2).setOnClickListener(captor.getValue());
+        verify(mShadeCarrier3).setOnClickListener(captor.getValue());
 
         assertThat(mNoCarrierTextView.getOnClickListener()).isSameInstanceAs(captor.getValue());
-        verify(mQSCarrierGroup, never()).setOnClickListener(any());
+        verify(mShadeCarrierGroup, never()).setOnClickListener(any());
     }
 
     @Test
@@ -402,10 +402,10 @@
         ArgumentCaptor<View.OnClickListener> captor =
                 ArgumentCaptor.forClass(View.OnClickListener.class);
 
-        verify(mQSCarrier1).setOnClickListener(captor.capture());
-        when(mQSCarrier1.isVisibleToUser()).thenReturn(false);
+        verify(mShadeCarrier1).setOnClickListener(captor.capture());
+        when(mShadeCarrier1.isVisibleToUser()).thenReturn(false);
 
-        captor.getValue().onClick(mQSCarrier1);
+        captor.getValue().onClick(mShadeCarrier1);
         verifyZeroInteractions(mActivityStarter);
     }
 
@@ -415,17 +415,17 @@
                 ArgumentCaptor.forClass(View.OnClickListener.class);
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
 
-        verify(mQSCarrier1).setOnClickListener(listenerCaptor.capture());
-        when(mQSCarrier1.isVisibleToUser()).thenReturn(true);
+        verify(mShadeCarrier1).setOnClickListener(listenerCaptor.capture());
+        when(mShadeCarrier1.isVisibleToUser()).thenReturn(true);
 
-        listenerCaptor.getValue().onClick(mQSCarrier1);
+        listenerCaptor.getValue().onClick(mShadeCarrier1);
         verify(mActivityStarter)
                 .postStartActivityDismissingKeyguard(intentCaptor.capture(), anyInt());
         assertThat(intentCaptor.getValue().getAction())
                 .isEqualTo(Settings.ACTION_WIRELESS_SETTINGS);
     }
 
-    private class FakeSlotIndexResolver implements QSCarrierGroupController.SlotIndexResolver {
+    private class FakeSlotIndexResolver implements ShadeCarrierGroupController.SlotIndexResolver {
         public boolean overrideInvalid;
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java
similarity index 67%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java
index 9115ab3..4461310 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2020 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.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.carrier;
+package com.android.systemui.shade.carrier;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -39,9 +39,9 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
-public class QSCarrierTest extends SysuiTestCase {
+public class ShadeCarrierTest extends SysuiTestCase {
 
-    private QSCarrier mQSCarrier;
+    private ShadeCarrier mShadeCarrier;
     private TestableLooper mTestableLooper;
     private int mSignalIconId;
 
@@ -51,7 +51,7 @@
         LayoutInflater inflater = LayoutInflater.from(mContext);
         mContext.ensureTestableResources();
         mTestableLooper.runWithLooper(() ->
-                mQSCarrier = (QSCarrier) inflater.inflate(R.layout.qs_carrier, null));
+                mShadeCarrier = (ShadeCarrier) inflater.inflate(R.layout.shade_carrier, null));
 
         // In this case, the id is an actual drawable id
         mSignalIconId = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0];
@@ -61,76 +61,76 @@
     public void testUpdateState_first() {
         CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
 
-        assertTrue(mQSCarrier.updateState(c, false));
+        assertTrue(mShadeCarrier.updateState(c, false));
     }
 
     @Test
     public void testUpdateState_same() {
         CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
 
-        assertTrue(mQSCarrier.updateState(c, false));
-        assertFalse(mQSCarrier.updateState(c, false));
+        assertTrue(mShadeCarrier.updateState(c, false));
+        assertFalse(mShadeCarrier.updateState(c, false));
     }
 
     @Test
     public void testUpdateState_changed() {
         CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
 
-        assertTrue(mQSCarrier.updateState(c, false));
+        assertTrue(mShadeCarrier.updateState(c, false));
 
         CellSignalState other = c.changeVisibility(false);
 
-        assertTrue(mQSCarrier.updateState(other, false));
+        assertTrue(mShadeCarrier.updateState(other, false));
     }
 
     @Test
     public void testUpdateState_singleCarrier_first() {
         CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
 
-        assertTrue(mQSCarrier.updateState(c, true));
+        assertTrue(mShadeCarrier.updateState(c, true));
     }
 
     @Test
     public void testUpdateState_singleCarrier_noShowIcon() {
         CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
 
-        mQSCarrier.updateState(c, true);
+        mShadeCarrier.updateState(c, true);
 
-        assertEquals(View.GONE, mQSCarrier.getRSSIView().getVisibility());
+        assertEquals(View.GONE, mShadeCarrier.getRSSIView().getVisibility());
     }
 
     @Test
     public void testUpdateState_multiCarrier_showIcon() {
         CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
 
-        mQSCarrier.updateState(c, false);
+        mShadeCarrier.updateState(c, false);
 
-        assertEquals(View.VISIBLE, mQSCarrier.getRSSIView().getVisibility());
+        assertEquals(View.VISIBLE, mShadeCarrier.getRSSIView().getVisibility());
     }
 
     @Test
     public void testUpdateState_changeSingleMultiSingle() {
         CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
 
-        mQSCarrier.updateState(c, true);
-        assertEquals(View.GONE, mQSCarrier.getRSSIView().getVisibility());
+        mShadeCarrier.updateState(c, true);
+        assertEquals(View.GONE, mShadeCarrier.getRSSIView().getVisibility());
 
-        mQSCarrier.updateState(c, false);
-        assertEquals(View.VISIBLE, mQSCarrier.getRSSIView().getVisibility());
+        mShadeCarrier.updateState(c, false);
+        assertEquals(View.VISIBLE, mShadeCarrier.getRSSIView().getVisibility());
 
-        mQSCarrier.updateState(c, true);
-        assertEquals(View.GONE, mQSCarrier.getRSSIView().getVisibility());
+        mShadeCarrier.updateState(c, true);
+        assertEquals(View.GONE, mShadeCarrier.getRSSIView().getVisibility());
     }
 
     @Test
     public void testCarrierNameMaxWidth_smallScreen_fromResource() {
         int maxEms = 10;
-        mContext.getOrCreateTestableResources().addOverride(R.integer.qs_carrier_max_em, maxEms);
+        mContext.getOrCreateTestableResources().addOverride(R.integer.shade_carrier_max_em, maxEms);
         mContext.getOrCreateTestableResources()
                 .addOverride(R.bool.config_use_large_screen_shade_header, false);
-        TextView carrierText = mQSCarrier.requireViewById(R.id.qs_carrier_text);
+        TextView carrierText = mShadeCarrier.requireViewById(R.id.shade_carrier_text);
 
-        mQSCarrier.onConfigurationChanged(mContext.getResources().getConfiguration());
+        mShadeCarrier.onConfigurationChanged(mContext.getResources().getConfiguration());
 
         assertEquals(maxEms, carrierText.getMaxEms());
     }
@@ -138,12 +138,12 @@
     @Test
     public void testCarrierNameMaxWidth_largeScreen_maxInt() {
         int maxEms = 10;
-        mContext.getOrCreateTestableResources().addOverride(R.integer.qs_carrier_max_em, maxEms);
+        mContext.getOrCreateTestableResources().addOverride(R.integer.shade_carrier_max_em, maxEms);
         mContext.getOrCreateTestableResources()
                 .addOverride(R.bool.config_use_large_screen_shade_header, true);
-        TextView carrierText = mQSCarrier.requireViewById(R.id.qs_carrier_text);
+        TextView carrierText = mShadeCarrier.requireViewById(R.id.shade_carrier_text);
 
-        mQSCarrier.onConfigurationChanged(mContext.getResources().getConfiguration());
+        mShadeCarrier.onConfigurationChanged(mContext.getResources().getConfiguration());
 
         assertEquals(Integer.MAX_VALUE, carrierText.getMaxEms());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 569f90b..4438b98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -614,9 +614,8 @@
     public void onBiometricHelp_coEx_faceFailure() {
         createController();
 
-        // GIVEN unlocking with fingerprint is possible
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt()))
-                .thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         String message = "A message";
         mController.setVisible(true);
@@ -641,9 +640,8 @@
     public void onBiometricHelp_coEx_faceUnavailable() {
         createController();
 
-        // GIVEN unlocking with fingerprint is possible
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt()))
-                .thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         String message = "A message";
         mController.setVisible(true);
@@ -664,6 +662,35 @@
                 mContext.getString(R.string.keyguard_suggest_fingerprint));
     }
 
+
+    @Test
+    public void onBiometricHelp_coEx_faceUnavailable_fpNotAllowed() {
+        createController();
+
+        // GIVEN unlocking with fingerprint is possible but not allowed
+        setupFingerprintUnlockPossible(true);
+        when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed())
+                .thenReturn(false);
+
+        String message = "A message";
+        mController.setVisible(true);
+
+        // WHEN there's a face unavailable message
+        mController.getKeyguardCallback().onBiometricHelp(
+                BIOMETRIC_HELP_FACE_NOT_AVAILABLE,
+                message,
+                BiometricSourceType.FACE);
+
+        // THEN show sequential messages such as: 'face unlock unavailable' and
+        // 'try fingerprint instead'
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                message);
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
     @Test
     public void onBiometricHelp_coEx_fpFailure_faceAlreadyUnlocked() {
         createController();
@@ -818,8 +845,7 @@
     @Test
     public void faceErrorTimeout_whenFingerprintEnrolled_doesNotShowMessage() {
         createController();
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(true);
+        fingerprintUnlockIsPossibleAndAllowed();
         String message = "A message";
 
         mController.setVisible(true);
@@ -832,9 +858,8 @@
     public void sendFaceHelpMessages_fingerprintEnrolled() {
         createController();
 
-        // GIVEN fingerprint enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         // WHEN help messages received that are allowed to show
         final String helpString = "helpString";
@@ -859,9 +884,8 @@
     public void doNotSendMostFaceHelpMessages_fingerprintEnrolled() {
         createController();
 
-        // GIVEN fingerprint enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         // WHEN help messages received that aren't supposed to show
         final String helpString = "helpString";
@@ -886,9 +910,8 @@
     public void sendAllFaceHelpMessages_fingerprintNotEnrolled() {
         createController();
 
-        // GIVEN fingerprint NOT enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(false);
+        // GIVEN fingerprint NOT possible
+        fingerprintUnlockIsNotPossible();
 
         // WHEN help messages received
         final Set<CharSequence> helpStrings = new HashSet<>();
@@ -917,9 +940,8 @@
     public void sendTooDarkFaceHelpMessages_onTimeout_noFpEnrolled() {
         createController();
 
-        // GIVEN fingerprint NOT enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(false);
+        // GIVEN fingerprint not possible
+        fingerprintUnlockIsNotPossible();
 
         // WHEN help message received and deferred message is valid
         final String helpString = "helpMsg";
@@ -948,9 +970,8 @@
     public void sendTooDarkFaceHelpMessages_onTimeout_fingerprintEnrolled() {
         createController();
 
-        // GIVEN fingerprint enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         // WHEN help message received and deferredMessage is valid
         final String helpString = "helpMsg";
@@ -1500,7 +1521,7 @@
     @Test
     public void onBiometricError_faceLockedOutFirstTimeAndFpAllowed_showsTheFpFollowupMessage() {
         createController();
-        fingerprintUnlockIsPossible();
+        fingerprintUnlockIsPossibleAndAllowed();
         onFaceLockoutError("first lockout");
 
         verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
@@ -1559,7 +1580,7 @@
     @Test
     public void onBiometricError_faceLockedOutAgainAndFpAllowed_showsTheFpFollowupMessage() {
         createController();
-        fingerprintUnlockIsPossible();
+        fingerprintUnlockIsPossibleAndAllowed();
         onFaceLockoutError("first lockout");
         clearInvocations(mRotateTextViewController);
 
@@ -1668,7 +1689,7 @@
     public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsAvailable_showsMessage() {
         createController();
         screenIsTurningOn();
-        fingerprintUnlockIsPossible();
+        fingerprintUnlockIsPossibleAndAllowed();
 
         onFaceLockoutError("lockout error");
         verifyNoMoreInteractions(mRotateTextViewController);
@@ -1746,8 +1767,9 @@
         setupFingerprintUnlockPossible(false);
     }
 
-    private void fingerprintUnlockIsPossible() {
+    private void fingerprintUnlockIsPossibleAndAllowed() {
         setupFingerprintUnlockPossible(true);
+        when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(true);
     }
 
     private void setupFingerprintUnlockPossible(boolean possible) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 7b59cc2..08a9f31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -22,7 +22,7 @@
 import android.testing.TestableLooper.RunWithLooper
 import android.view.View
 import android.widget.FrameLayout
-import androidx.core.animation.AnimatorTestRule2
+import androidx.core.animation.AnimatorTestRule
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -70,7 +70,7 @@
     private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler
     private val fakeFeatureFlags = FakeFeatureFlags()
 
-    @get:Rule val animatorTestRule = AnimatorTestRule2()
+    @get:Rule val animatorTestRule = AnimatorTestRule()
 
     @Before
     fun setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index be3b723..aff705f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -18,7 +18,7 @@
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-import androidx.core.animation.AnimatorTestRule2
+import androidx.core.animation.AnimatorTestRule
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -51,7 +51,7 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
 
-    @get:Rule val animatorTestRule = AnimatorTestRule2()
+    @get:Rule val animatorTestRule = AnimatorTestRule()
 
     private val dumpManager: DumpManager = mock()
     private val headsUpManager: HeadsUpManager = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
index 30708a7..ac66ad9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
@@ -97,6 +97,59 @@
     }
 
     @Test
+    public void highImportanceConversation() {
+        // GIVEN notification is high importance and is a people notification
+        final Notification notification = new Notification.Builder(mContext, "test")
+                .build();
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setImportance(IMPORTANCE_HIGH)
+                .build();
+        when(mPeopleNotificationIdentifier
+                .getPeopleNotificationType(entry))
+                .thenReturn(TYPE_PERSON);
+
+        // THEN it is high priority conversation
+        assertTrue(mHighPriorityProvider.isHighPriorityConversation(entry));
+    }
+
+    @Test
+    public void lowImportanceConversation() {
+        // GIVEN notification is high importance and is a people notification
+        final Notification notification = new Notification.Builder(mContext, "test")
+                .build();
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setImportance(IMPORTANCE_LOW)
+                .build();
+        when(mPeopleNotificationIdentifier
+                .getPeopleNotificationType(entry))
+                .thenReturn(TYPE_PERSON);
+
+        // THEN it is low priority conversation
+        assertFalse(mHighPriorityProvider.isHighPriorityConversation(entry));
+    }
+
+    @Test
+    public void highImportanceConversationWhenAnyOfChildIsHighPriority() {
+        // GIVEN notification is high importance and is a people notification
+        final NotificationEntry summary = createNotifEntry(false);
+        final NotificationEntry lowPriorityChild = createNotifEntry(false);
+        final NotificationEntry highPriorityChild = createNotifEntry(true);
+        when(mPeopleNotificationIdentifier
+                .getPeopleNotificationType(summary))
+                .thenReturn(TYPE_PERSON);
+        final GroupEntry groupEntry = new GroupEntryBuilder()
+                .setParent(GroupEntry.ROOT_ENTRY)
+                .setSummary(summary)
+                .setChildren(List.of(lowPriorityChild, highPriorityChild))
+                .build();
+
+        // THEN the groupEntry is high priority conversation since it has a high priority child
+        assertTrue(mHighPriorityProvider.isHighPriorityConversation(groupEntry));
+    }
+
+    @Test
     public void messagingStyle() {
         // GIVEN notification is low importance but has messaging style
         final Notification notification = new Notification.Builder(mContext, "test")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index 742fcf5..55ea3157 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -17,6 +17,9 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.app.NotificationChannel
+import android.app.NotificationManager.IMPORTANCE_DEFAULT
+import android.app.NotificationManager.IMPORTANCE_HIGH
+import android.app.NotificationManager.IMPORTANCE_LOW
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
@@ -31,10 +34,13 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.icon.ConversationIconManager
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.withArgCaptor
@@ -55,7 +61,8 @@
 class ConversationCoordinatorTest : SysuiTestCase() {
     // captured listeners and pluggables:
     private lateinit var promoter: NotifPromoter
-    private lateinit var peopleSectioner: NotifSectioner
+    private lateinit var peopleAlertingSectioner: NotifSectioner
+    private lateinit var peopleSilentSectioner: NotifSectioner
     private lateinit var peopleComparator: NotifComparator
     private lateinit var beforeRenderListListener: OnBeforeRenderListListener
 
@@ -76,6 +83,7 @@
         coordinator = ConversationCoordinator(
             peopleNotificationIdentifier,
             conversationIconManager,
+            HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()),
             headerController
         )
         whenever(channel.isImportantConversation).thenReturn(true)
@@ -90,12 +98,13 @@
             verify(pipeline).addOnBeforeRenderListListener(capture())
         }
 
-        peopleSectioner = coordinator.sectioner
-        peopleComparator = peopleSectioner.comparator!!
+        peopleAlertingSectioner = coordinator.peopleAlertingSectioner
+        peopleSilentSectioner = coordinator.peopleSilentSectioner
+        peopleComparator = peopleAlertingSectioner.comparator!!
 
         entry = NotificationEntryBuilder().setChannel(channel).build()
 
-        val section = NotifSection(peopleSectioner, 0)
+        val section = NotifSection(peopleAlertingSectioner, 0)
         entryA = NotificationEntryBuilder().setChannel(channel)
             .setSection(section).setTag("A").build()
         entryB = NotificationEntryBuilder().setChannel(channel)
@@ -129,13 +138,67 @@
     }
 
     @Test
-    fun testInPeopleSection() {
-        whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry))
-            .thenReturn(TYPE_PERSON)
+    fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() {
+        // GIVEN
+        val alertingEntry = NotificationEntryBuilder().setChannel(channel)
+                .setImportance(IMPORTANCE_DEFAULT).build()
+        whenever(peopleNotificationIdentifier.getPeopleNotificationType(alertingEntry))
+                .thenReturn(TYPE_PERSON)
 
-        // only put people notifications in this section
-        assertTrue(peopleSectioner.isInSection(entry))
-        assertFalse(peopleSectioner.isInSection(NotificationEntryBuilder().build()))
+        // put alerting people notifications in this section
+        assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue()
+       }
+
+    @Test
+    fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() {
+        // GIVEN
+        val silentEntry = NotificationEntryBuilder().setChannel(channel)
+                .setImportance(IMPORTANCE_LOW).build()
+        whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry))
+                .thenReturn(TYPE_PERSON)
+
+        // THEN put silent people notifications in this section
+        assertThat(peopleSilentSectioner.isInSection(silentEntry)).isTrue()
+        // People Alerting sectioning happens before the silent one.
+        // It claims high important conversations and rest of conversations will be considered as silent.
+        assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isFalse()
+    }
+
+    @Test
+    fun testNotInPeopleSection() {
+        // GIVEN
+        val entry = NotificationEntryBuilder().setChannel(channel)
+                .setImportance(IMPORTANCE_LOW).build()
+        val importantEntry = NotificationEntryBuilder().setChannel(channel)
+                .setImportance(IMPORTANCE_HIGH).build()
+        whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry))
+                .thenReturn(TYPE_NON_PERSON)
+        whenever(peopleNotificationIdentifier.getPeopleNotificationType(importantEntry))
+                .thenReturn(TYPE_NON_PERSON)
+
+        // THEN - only put people notification either silent or alerting
+        assertThat(peopleSilentSectioner.isInSection(entry)).isFalse()
+        assertThat(peopleAlertingSectioner.isInSection(importantEntry)).isFalse()
+    }
+
+    @Test
+    fun testInAlertingPeopleSectionWhenThereIsAnImportantChild(){
+        // GIVEN
+        val altChildA = NotificationEntryBuilder().setTag("A")
+                .setImportance(IMPORTANCE_DEFAULT).build()
+        val altChildB = NotificationEntryBuilder().setTag("B")
+                .setImportance(IMPORTANCE_LOW).build()
+        val summary = NotificationEntryBuilder().setId(2)
+                .setImportance(IMPORTANCE_LOW).setChannel(channel).build()
+        val groupEntry = GroupEntryBuilder()
+                .setParent(GroupEntry.ROOT_ENTRY)
+                .setSummary(summary)
+                .setChildren(listOf(altChildA, altChildB))
+                .build()
+        whenever(peopleNotificationIdentifier.getPeopleNotificationType(summary))
+                .thenReturn(TYPE_PERSON)
+        // THEN
+        assertThat(peopleAlertingSectioner.isInSection(groupEntry)).isTrue()
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index d5c0c55..3d1253e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -52,7 +52,6 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
 import com.android.systemui.statusbar.notification.collection.render.NodeController;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
 
@@ -73,7 +72,6 @@
 
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private HighPriorityProvider mHighPriorityProvider;
-    @Mock private SectionStyleProvider mSectionStyleProvider;
     @Mock private NotifPipeline mNotifPipeline;
     @Mock private NodeController mAlertingHeaderController;
     @Mock private NodeController mSilentNodeController;
@@ -100,7 +98,6 @@
         mRankingCoordinator = new RankingCoordinator(
                 mStatusBarStateController,
                 mHighPriorityProvider,
-                mSectionStyleProvider,
                 mAlertingHeaderController,
                 mSilentHeaderController,
                 mSilentNodeController);
@@ -108,7 +105,6 @@
         mEntry.setRanking(getRankingForUnfilteredNotif().build());
 
         mRankingCoordinator.attach(mNotifPipeline);
-        verify(mSectionStyleProvider).setMinimizedSections(any());
         verify(mNotifPipeline, times(2)).addPreGroupFilter(mNotifFilterCaptor.capture());
         mCapturedSuspendedFilter = mNotifFilterCaptor.getAllValues().get(0);
         mCapturedDozingFilter = mNotifFilterCaptor.getAllValues().get(1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
new file mode 100644
index 0000000..cbb0894
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
@@ -0,0 +1,78 @@
+package com.android.systemui.statusbar.notification.interruption
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_NOT_IMPORTANT_ENOUGH
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.DecisionImpl
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.FullScreenIntentDecisionImpl
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationInterruptStateProviderWrapperTest : SysuiTestCase() {
+
+    @Test
+    fun decisionOfTrue() {
+        assertTrue(DecisionImpl.of(true).shouldInterrupt)
+    }
+
+    @Test
+    fun decisionOfFalse() {
+        assertFalse(DecisionImpl.of(false).shouldInterrupt)
+    }
+
+    @Test
+    fun decisionOfTrueInterned() {
+        assertEquals(DecisionImpl.of(true), DecisionImpl.of(true))
+    }
+
+    @Test
+    fun decisionOfFalseInterned() {
+        assertEquals(DecisionImpl.of(false), DecisionImpl.of(false))
+    }
+
+    @Test
+    fun fullScreenIntentDecisionShouldInterrupt() {
+        makeFsiDecision(FSI_DEVICE_NOT_INTERACTIVE).let {
+            assertTrue(it.shouldInterrupt)
+            assertFalse(it.wouldInterruptWithoutDnd)
+        }
+    }
+
+    @Test
+    fun fullScreenIntentDecisionShouldNotInterrupt() {
+        makeFsiDecision(NO_FSI_NOT_IMPORTANT_ENOUGH).let {
+            assertFalse(it.shouldInterrupt)
+            assertFalse(it.wouldInterruptWithoutDnd)
+        }
+    }
+
+    @Test
+    fun fullScreenIntentDecisionWouldInterruptWithoutDnd() {
+        makeFsiDecision(NO_FSI_SUPPRESSED_ONLY_BY_DND).let {
+            assertFalse(it.shouldInterrupt)
+            assertTrue(it.wouldInterruptWithoutDnd)
+        }
+    }
+
+    @Test
+    fun fullScreenIntentDecisionWouldNotInterruptEvenWithoutDnd() {
+        makeFsiDecision(NO_FSI_SUPPRESSED_BY_DND).let {
+            assertFalse(it.shouldInterrupt)
+            assertFalse(it.wouldInterruptWithoutDnd)
+        }
+    }
+
+    private fun makeFsiDecision(originalDecision: FullScreenIntentDecision) =
+        FullScreenIntentDecisionImpl(NotificationEntryBuilder().build(), originalDecision)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
new file mode 100644
index 0000000..14e5f9e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.shelf.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class NotificationShelfInteractorTest : SysuiTestCase() {
+
+    private val keyguardRepository = FakeKeyguardRepository()
+    private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+    private val underTest =
+        NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository)
+
+    @Test
+    fun shelfIsNotStatic_whenKeyguardNotShowing() = runTest {
+        val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+        keyguardRepository.setKeyguardShowing(false)
+
+        assertThat(shelfStatic).isFalse()
+    }
+
+    @Test
+    fun shelfIsNotStatic_whenKeyguardShowingAndNotBypass() = runTest {
+        val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+        keyguardRepository.setKeyguardShowing(true)
+        deviceEntryFaceAuthRepository.isBypassEnabled.value = false
+
+        assertThat(shelfStatic).isFalse()
+    }
+
+    @Test
+    fun shelfIsStatic_whenBypass() = runTest {
+        val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+        keyguardRepository.setKeyguardShowing(true)
+        deviceEntryFaceAuthRepository.isBypassEnabled.value = true
+
+        assertThat(shelfStatic).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
new file mode 100644
index 0000000..6c5fb8b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.shelf.ui.viewmodel
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class NotificationShelfViewModelTest : SysuiTestCase() {
+
+    private val keyguardRepository = FakeKeyguardRepository()
+    private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+    private val interactor =
+        NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository)
+    private val underTest = NotificationShelfViewModel(interactor)
+
+    @Test
+    fun canModifyColorOfNotifications_whenKeyguardNotShowing() = runTest {
+        val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+        keyguardRepository.setKeyguardShowing(false)
+
+        assertThat(canModifyNotifColor).isTrue()
+    }
+
+    @Test
+    fun canModifyColorOfNotifications_whenKeyguardShowingAndNotBypass() = runTest {
+        val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+        keyguardRepository.setKeyguardShowing(true)
+        deviceEntryFaceAuthRepository.isBypassEnabled.value = false
+
+        assertThat(canModifyNotifColor).isTrue()
+    }
+
+    @Test
+    fun cannotModifyColorOfNotifications_whenBypass() = runTest {
+        val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+        keyguardRepository.setKeyguardShowing(true)
+        deviceEntryFaceAuthRepository.isBypassEnabled.value = true
+
+        assertThat(canModifyNotifColor).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 32f0adf..48710a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -133,6 +133,7 @@
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeControllerImpl;
 import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.shade.ShadeLogger;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -221,6 +222,7 @@
     @Mock private NotificationListContainer mNotificationListContainer;
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
     @Mock private NotificationPanelViewController mNotificationPanelViewController;
+    @Mock private ShadeLogger mShadeLogger;
     @Mock private NotificationPanelView mNotificationPanelView;
     @Mock private QuickSettingsController mQuickSettingsController;
     @Mock private IStatusBarService mBarService;
@@ -469,6 +471,7 @@
                 mKeyguardViewMediator,
                 new DisplayMetrics(),
                 mMetricsLogger,
+                mShadeLogger,
                 mUiBgExecutor,
                 mNotificationMediaManager,
                 mLockscreenUserManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index f9c72d5..3591c17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -67,6 +67,8 @@
 
     override val mobileIsDefault = MutableStateFlow(false)
 
+    override val hasCarrierMergedConnection = MutableStateFlow(false)
+
     override val defaultConnectionIsValidated = MutableStateFlow(false)
 
     private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
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 9d294cf..7cc59b6 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
@@ -68,6 +68,7 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
 import org.junit.After
 import org.junit.Assert.assertThrows
 import org.junit.Assert.assertTrue
@@ -766,28 +767,6 @@
             job.cancel()
         }
 
-    /** Regression test for b/272586234. */
-    @Test
-    fun mobileIsDefault_carrierMergedViaWifi_isDefault() =
-        runBlocking(IMMEDIATE) {
-            val carrierMergedInfo =
-                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
-            val caps =
-                mock<NetworkCapabilities>().also {
-                    whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
-                    whenever(it.transportInfo).thenReturn(carrierMergedInfo)
-                }
-
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
-
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-
-            assertThat(latest).isTrue()
-
-            job.cancel()
-        }
-
     @Test
     fun mobileIsDefault_carrierMergedViaMobile_isDefault() =
         runBlocking(IMMEDIATE) {
@@ -809,49 +788,6 @@
             job.cancel()
         }
 
-    /** Regression test for b/272586234. */
-    @Test
-    fun mobileIsDefault_carrierMergedViaWifiWithVcnTransport_isDefault() =
-        runBlocking(IMMEDIATE) {
-            val carrierMergedInfo =
-                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
-            val caps =
-                mock<NetworkCapabilities>().also {
-                    whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
-                    whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
-                }
-
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
-
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-
-            assertThat(latest).isTrue()
-
-            job.cancel()
-        }
-
-    @Test
-    fun mobileIsDefault_carrierMergedViaMobileWithVcnTransport_isDefault() =
-        runBlocking(IMMEDIATE) {
-            val carrierMergedInfo =
-                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
-            val caps =
-                mock<NetworkCapabilities>().also {
-                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
-                    whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
-                }
-
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
-
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-
-            assertThat(latest).isTrue()
-
-            job.cancel()
-        }
-
     @Test
     fun mobileIsDefault_wifiDefault_mobileNotDefault() =
         runBlocking(IMMEDIATE) {
@@ -888,6 +824,195 @@
             job.cancel()
         }
 
+    /** Regression test for b/272586234. */
+    @Test
+    fun hasCarrierMergedConnection_carrierMergedViaWifi_isTrue() =
+        runBlocking(IMMEDIATE) {
+            val carrierMergedInfo =
+                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+            val caps =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+                }
+
+            var latest: Boolean? = null
+            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+            yield()
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun hasCarrierMergedConnection_carrierMergedViaMobile_isTrue() =
+        runBlocking(IMMEDIATE) {
+            val carrierMergedInfo =
+                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+            val caps =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+                }
+
+            var latest: Boolean? = null
+            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+            yield()
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    /** Regression test for b/272586234. */
+    @Test
+    fun hasCarrierMergedConnection_carrierMergedViaWifiWithVcnTransport_isTrue() =
+        runBlocking(IMMEDIATE) {
+            val carrierMergedInfo =
+                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+            val caps =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+                }
+
+            var latest: Boolean? = null
+            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+            yield()
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun hasCarrierMergedConnection_carrierMergedViaMobileWithVcnTransport_isTrue() =
+        runBlocking(IMMEDIATE) {
+            val carrierMergedInfo =
+                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+            val caps =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+                }
+
+            var latest: Boolean? = null
+            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+            yield()
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingWifi_isTrue() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+
+            val underlyingNetwork = mock<Network>()
+            val carrierMergedInfo =
+                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+            val underlyingWifiCapabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+                }
+            whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+                .thenReturn(underlyingWifiCapabilities)
+
+            // WHEN the main capabilities have an underlying carrier merged network via WIFI
+            // transport and WifiInfo
+            val mainCapabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(null)
+                    whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+                }
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+            yield()
+
+            // THEN there's a carrier merged connection
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingCellular_isTrue() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+
+            val underlyingCarrierMergedNetwork = mock<Network>()
+            val carrierMergedInfo =
+                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+            val underlyingCapabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+                }
+            whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+                .thenReturn(underlyingCapabilities)
+
+            // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
+            // transport and VcnTransportInfo
+            val mainCapabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(null)
+                    whenever(it.underlyingNetworks)
+                        .thenReturn(listOf(underlyingCarrierMergedNetwork))
+                }
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+            yield()
+
+            // THEN there's a carrier merged connection
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    /** Regression test for b/272586234. */
+    @Test
+    fun hasCarrierMergedConnection_defaultNotCarrierMerged_butWifiRepoHasCarrierMerged_isTrue() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+
+            // WHEN the default callback isn't carrier merged
+            val carrierMergedInfo =
+                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(false) }
+            val caps =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+                }
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+            yield()
+
+            // BUT the wifi repo has gotten updates that it *is* carrier merged
+            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            yield()
+
+            // THEN hasCarrierMergedConnection is true
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
     @Test
     fun defaultConnectionIsValidated_startsAsFalse() {
         assertThat(underTest.defaultConnectionIsValidated.value).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index c5ceaca..dc68386 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -456,7 +456,50 @@
         }
 
     @Test
-    fun mobileIsDefault_usesRepoValue() =
+    fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+
+            connectionsRepository.mobileIsDefault.value = false
+            connectionsRepository.hasCarrierMergedConnection.value = false
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+
+            connectionsRepository.mobileIsDefault.value = true
+            connectionsRepository.hasCarrierMergedConnection.value = false
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    /** Regression test for b/272586234. */
+    @Test
+    fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+
+            connectionsRepository.mobileIsDefault.value = false
+            connectionsRepository.hasCarrierMergedConnection.value = true
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun mobileIsDefault_updatesWhenRepoUpdates() =
         testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
@@ -467,7 +510,7 @@
             connectionsRepository.mobileIsDefault.value = false
             assertThat(latest).isFalse()
 
-            connectionsRepository.mobileIsDefault.value = true
+            connectionsRepository.hasCarrierMergedConnection.value = true
             assertThat(latest).isTrue()
 
             job.cancel()
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 1b6ab4d..297cb9d 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
@@ -179,15 +179,71 @@
         }
 
     @Test
-    fun iconId_cutout_whenDefaultDataDisabled() =
+    fun icon_usesLevelFromInteractor() =
+        testScope.runTest {
+            var latest: SignalIconModel? = null
+            val job = underTest.icon.onEach { latest = it }.launchIn(this)
+
+            interactor.level.value = 3
+            assertThat(latest!!.level).isEqualTo(3)
+
+            interactor.level.value = 1
+            assertThat(latest!!.level).isEqualTo(1)
+
+            job.cancel()
+        }
+
+    @Test
+    fun icon_usesNumberOfLevelsFromInteractor() =
+        testScope.runTest {
+            var latest: SignalIconModel? = null
+            val job = underTest.icon.onEach { latest = it }.launchIn(this)
+
+            interactor.numberOfLevels.value = 5
+            assertThat(latest!!.numberOfLevels).isEqualTo(5)
+
+            interactor.numberOfLevels.value = 2
+            assertThat(latest!!.numberOfLevels).isEqualTo(2)
+
+            job.cancel()
+        }
+
+    @Test
+    fun icon_defaultDataDisabled_showExclamationTrue() =
         testScope.runTest {
             interactor.setIsDefaultDataEnabled(false)
 
             var latest: SignalIconModel? = null
             val job = underTest.icon.onEach { latest = it }.launchIn(this)
-            val expected = defaultSignal(level = 1, connected = false)
 
-            assertThat(latest).isEqualTo(expected)
+            assertThat(latest!!.showExclamationMark).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun icon_defaultConnectionFailed_showExclamationTrue() =
+        testScope.runTest {
+            interactor.isDefaultConnectionFailed.value = true
+
+            var latest: SignalIconModel? = null
+            val job = underTest.icon.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest!!.showExclamationMark).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun icon_enabledAndNotFailed_showExclamationFalse() =
+        testScope.runTest {
+            interactor.setIsDefaultDataEnabled(true)
+            interactor.isDefaultConnectionFailed.value = false
+
+            var latest: SignalIconModel? = null
+            val job = underTest.icon.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest!!.showExclamationMark).isFalse()
 
             job.cancel()
         }
@@ -572,16 +628,14 @@
 
     companion object {
         private const val SUB_1_ID = 1
+        private const val NUM_LEVELS = 4
 
         /** Convenience constructor for these tests */
-        fun defaultSignal(
-            level: Int = 1,
-            connected: Boolean = true,
-        ): SignalIconModel {
-            return SignalIconModel(level, numberOfLevels = 4, showExclamationMark = !connected)
+        fun defaultSignal(level: Int = 1): SignalIconModel {
+            return SignalIconModel(level, NUM_LEVELS, showExclamationMark = false)
         }
 
         fun emptySignal(): SignalIconModel =
-            SignalIconModel(level = 0, numberOfLevels = 4, showExclamationMark = true)
+            SignalIconModel(level = 0, numberOfLevels = NUM_LEVELS, showExclamationMark = true)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
index 87d4f5c..661002d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.DEFAULT_HIDDEN_ICONS_RESOURCE
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.HIDDEN_ICONS_TUNABLE_KEY
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
@@ -491,6 +492,111 @@
         }
 
     @Test
+    fun defaultConnections_nullUnderlyingInfo_noError() {
+        val mainCapabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(null)
+                whenever(it.underlyingNetworks).thenReturn(null)
+            }
+
+        getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+        // No assert, just verify no error
+    }
+
+    @Test
+    fun defaultConnections_underlyingInfoHasNullCapabilities_noError() {
+        val underlyingNetworkWithNull = mock<Network>()
+        whenever(connectivityManager.getNetworkCapabilities(underlyingNetworkWithNull))
+            .thenReturn(null)
+
+        val mainCapabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(null)
+                whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetworkWithNull))
+            }
+
+        getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+        // No assert, just verify no error
+    }
+
+    // This test verifies our internal API for completeness, but we don't expect this case to ever
+    // happen in practice.
+    @Test
+    fun defaultConnections_cellular_underlyingCarrierMergedViaWifi_allDefault() =
+        testScope.runTest {
+            var latest: DefaultConnectionModel? = null
+            val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+
+            // Underlying carrier merged network
+            val underlyingCarrierMergedNetwork = mock<Network>()
+            val carrierMergedInfo =
+                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+            val underlyingCapabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+                }
+            whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+                .thenReturn(underlyingCapabilities)
+
+            // Main network with underlying network
+            val mainCapabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(null)
+                    whenever(it.underlyingNetworks)
+                        .thenReturn(listOf(underlyingCarrierMergedNetwork))
+                }
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+            assertThat(latest!!.mobile.isDefault).isTrue()
+            assertThat(latest!!.carrierMerged.isDefault).isTrue()
+            assertThat(latest!!.wifi.isDefault).isTrue()
+
+            job.cancel()
+        }
+
+    /** Test for b/225902574. */
+    @Test
+    fun defaultConnections_cellular_underlyingCarrierMergedViaMobileWithVcnTransport_allDefault() =
+        testScope.runTest {
+            var latest: DefaultConnectionModel? = null
+            val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+
+            // Underlying carrier merged network
+            val underlyingCarrierMergedNetwork = mock<Network>()
+            val carrierMergedInfo =
+                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+            val underlyingCapabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+                }
+            whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+                .thenReturn(underlyingCapabilities)
+
+            // Main network with underlying network
+            val mainCapabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(null)
+                    whenever(it.underlyingNetworks)
+                        .thenReturn(listOf(underlyingCarrierMergedNetwork))
+                }
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+            assertThat(latest!!.mobile.isDefault).isTrue()
+            assertThat(latest!!.carrierMerged.isDefault).isTrue()
+            assertThat(latest!!.wifi.isDefault).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
     fun defaultConnections_multipleTransports_multipleDefault() =
         testScope.runTest {
             var latest: DefaultConnectionModel? = null
@@ -548,6 +654,279 @@
             job.cancel()
         }
 
+    @Test
+    fun getMainOrUnderlyingWifiInfo_wifi_hasInfo() {
+        val wifiInfo = mock<WifiInfo>()
+        val capabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(wifiInfo)
+            }
+
+        val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+        assertThat(result).isEqualTo(wifiInfo)
+    }
+
+    @Test
+    fun getMainOrUnderlyingWifiInfo_vcnWithWifi_hasInfo() {
+        val wifiInfo = mock<WifiInfo>()
+        val vcnInfo = VcnTransportInfo(wifiInfo)
+        val capabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(vcnInfo)
+            }
+
+        val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+        assertThat(result).isEqualTo(wifiInfo)
+    }
+
+    @Test
+    fun getMainOrUnderlyingWifiInfo_notCellularOrWifiTransport_noInfo() {
+        val capabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
+                whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
+                whenever(it.transportInfo).thenReturn(mock<WifiInfo>())
+            }
+
+        val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+        assertThat(result).isNull()
+    }
+
+    @Test
+    fun getMainOrUnderlyingWifiInfo_cellular_underlyingWifi_hasInfo() {
+        val underlyingNetwork = mock<Network>()
+        val underlyingWifiInfo = mock<WifiInfo>()
+        val underlyingWifiCapabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(underlyingWifiInfo)
+            }
+        whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+            .thenReturn(underlyingWifiCapabilities)
+
+        // WHEN the main capabilities have an underlying wifi network
+        val mainCapabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(null)
+                whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+            }
+
+        val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+        // THEN we fetch the underlying wifi info
+        assertThat(result).isEqualTo(underlyingWifiInfo)
+    }
+
+    @Test
+    fun getMainOrUnderlyingWifiInfo_notCellular_underlyingWifi_noInfo() {
+        val underlyingNetwork = mock<Network>()
+        val underlyingWifiInfo = mock<WifiInfo>()
+        val underlyingWifiCapabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(underlyingWifiInfo)
+            }
+        whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+            .thenReturn(underlyingWifiCapabilities)
+
+        // WHEN the main capabilities have an underlying wifi network but is *not* CELLULAR
+        val mainCapabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true)
+                whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
+                whenever(it.transportInfo).thenReturn(null)
+                whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+            }
+
+        val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+        // THEN we DON'T fetch the underlying wifi info
+        assertThat(result).isNull()
+    }
+
+    @Test
+    fun getMainOrUnderlyingWifiInfo_cellular_underlyingVcnWithWifi_hasInfo() {
+        val wifiInfo = mock<WifiInfo>()
+        val underlyingNetwork = mock<Network>()
+        val underlyingVcnInfo = VcnTransportInfo(wifiInfo)
+        val underlyingWifiCapabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(underlyingVcnInfo)
+            }
+        whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+            .thenReturn(underlyingWifiCapabilities)
+
+        // WHEN the main capabilities have an underlying VCN network with wifi
+        val mainCapabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(null)
+                whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+            }
+
+        val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+        // THEN we fetch the wifi info
+        assertThat(result).isEqualTo(wifiInfo)
+    }
+
+    @Test
+    fun getMainOrUnderlyingWifiInfo_notCellular_underlyingVcnWithWifi_noInfo() {
+        val underlyingNetwork = mock<Network>()
+        val underlyingVcnInfo = VcnTransportInfo(mock<WifiInfo>())
+        val underlyingWifiCapabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(underlyingVcnInfo)
+            }
+        whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+            .thenReturn(underlyingWifiCapabilities)
+
+        // WHEN the main capabilities have an underlying wifi network but it is *not* CELLULAR
+        val mainCapabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true)
+                whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
+                whenever(it.transportInfo).thenReturn(null)
+                whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+            }
+
+        val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+        // THEN we DON'T fetch the underlying wifi info
+        assertThat(result).isNull()
+    }
+
+    @Test
+    fun getMainOrUnderlyingWifiInfo_cellular_underlyingCellularWithCarrierMerged_hasInfo() {
+        // Underlying carrier merged network
+        val underlyingCarrierMergedNetwork = mock<Network>()
+        val carrierMergedInfo =
+            mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+        val underlyingCapabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+            }
+        whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+            .thenReturn(underlyingCapabilities)
+
+        // Main network with underlying network
+        val mainCapabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(null)
+                whenever(it.underlyingNetworks).thenReturn(listOf(underlyingCarrierMergedNetwork))
+            }
+
+        val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+        assertThat(result).isEqualTo(carrierMergedInfo)
+        assertThat(result!!.isCarrierMerged).isTrue()
+    }
+
+    @Test
+    fun getMainOrUnderlyingWifiInfo_multipleUnderlying_usesFirstNonNull() {
+        // First underlying: Not wifi
+        val underlyingNotWifiNetwork = mock<Network>()
+        val underlyingNotWifiCapabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
+                whenever(it.transportInfo).thenReturn(null)
+            }
+        whenever(connectivityManager.getNetworkCapabilities(underlyingNotWifiNetwork))
+            .thenReturn(underlyingNotWifiCapabilities)
+
+        // Second underlying: wifi
+        val underlyingWifiNetwork1 = mock<Network>()
+        val underlyingWifiInfo1 = mock<WifiInfo>()
+        val underlyingWifiCapabilities1 =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(underlyingWifiInfo1)
+            }
+        whenever(connectivityManager.getNetworkCapabilities(underlyingWifiNetwork1))
+            .thenReturn(underlyingWifiCapabilities1)
+
+        // Third underlying: also wifi
+        val underlyingWifiNetwork2 = mock<Network>()
+        val underlyingWifiInfo2 = mock<WifiInfo>()
+        val underlyingWifiCapabilities2 =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(underlyingWifiInfo2)
+            }
+        whenever(connectivityManager.getNetworkCapabilities(underlyingWifiNetwork2))
+            .thenReturn(underlyingWifiCapabilities2)
+
+        // WHEN the main capabilities has multiple underlying networks
+        val mainCapabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(null)
+                whenever(it.underlyingNetworks)
+                    .thenReturn(
+                        listOf(
+                            underlyingNotWifiNetwork,
+                            underlyingWifiNetwork1,
+                            underlyingWifiNetwork2,
+                        )
+                    )
+            }
+
+        val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+        // THEN the first wifi one is used
+        assertThat(result).isEqualTo(underlyingWifiInfo1)
+    }
+
+    @Test
+    fun getMainOrUnderlyingWifiInfo_nestedUnderlying_doesNotLookAtNested() {
+        // WHEN there are two layers of underlying networks...
+
+        // Nested network
+        val nestedUnderlyingNetwork = mock<Network>()
+        val nestedWifiInfo = mock<WifiInfo>()
+        val nestedCapabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(nestedWifiInfo)
+            }
+        whenever(connectivityManager.getNetworkCapabilities(nestedUnderlyingNetwork))
+            .thenReturn(nestedCapabilities)
+
+        // Underlying network containing the nested network
+        val underlyingNetwork = mock<Network>()
+        val underlyingCapabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(null)
+                whenever(it.underlyingNetworks).thenReturn(listOf(nestedUnderlyingNetwork))
+            }
+        whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+            .thenReturn(underlyingCapabilities)
+
+        // Main network containing the underlying network, which contains the nested network
+        val mainCapabilities =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(null)
+                whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+            }
+
+        val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+        // THEN only the first layer is checked, and the first layer has no wifi info
+        assertThat(result).isNull()
+    }
+
     private fun createAndSetRepo() {
         underTest =
             ConnectivityRepositoryImpl(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index f69e9a3..d30e024 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -29,6 +29,7 @@
 import android.net.wifi.WifiInfo
 import android.net.wifi.WifiManager
 import android.net.wifi.WifiManager.TrafficStateCallback
+import android.net.wifi.WifiManager.UNKNOWN_SSID
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -49,16 +50,14 @@
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Executor
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.After
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.mockito.ArgumentMatchers.anyInt
@@ -80,9 +79,10 @@
     @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var wifiManager: WifiManager
     private lateinit var executor: Executor
-    private lateinit var scope: CoroutineScope
     private lateinit var connectivityRepository: ConnectivityRepository
 
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -96,7 +96,6 @@
             )
             .thenReturn(flowOf(Unit))
         executor = FakeExecutor(FakeSystemClock())
-        scope = CoroutineScope(IMMEDIATE)
 
         connectivityRepository =
             ConnectivityRepositoryImpl(
@@ -105,21 +104,16 @@
                 context,
                 mock(),
                 mock(),
-                scope,
+                testScope.backgroundScope,
                 mock(),
             )
 
         underTest = createRepo()
     }
 
-    @After
-    fun tearDown() {
-        scope.cancel()
-    }
-
     @Test
     fun isWifiEnabled_initiallyGetsWifiManagerValue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             whenever(wifiManager.isWifiEnabled).thenReturn(true)
 
             underTest = createRepo()
@@ -129,7 +123,7 @@
 
     @Test
     fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             // We need to call launch on the flows so that they start updating
             val networkJob = underTest.wifiNetwork.launchIn(this)
             val enabledJob = underTest.isWifiEnabled.launchIn(this)
@@ -152,7 +146,7 @@
 
     @Test
     fun isWifiEnabled_networkLost_valueUpdated() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             // We need to call launch on the flows so that they start updating
             val networkJob = underTest.wifiNetwork.launchIn(this)
             val enabledJob = underTest.isWifiEnabled.launchIn(this)
@@ -173,7 +167,7 @@
 
     @Test
     fun isWifiEnabled_intentsReceived_valueUpdated() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val intentFlow = MutableSharedFlow<Unit>()
             whenever(
                     broadcastDispatcher.broadcastFlow(
@@ -203,7 +197,7 @@
 
     @Test
     fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val intentFlow = MutableSharedFlow<Unit>()
             whenever(
                     broadcastDispatcher.broadcastFlow(
@@ -242,7 +236,7 @@
 
     @Test
     fun isWifiDefault_initiallyGetsDefault() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val job = underTest.isWifiDefault.launchIn(this)
 
             assertThat(underTest.isWifiDefault.value).isFalse()
@@ -252,7 +246,7 @@
 
     @Test
     fun isWifiDefault_wifiNetwork_isTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val job = underTest.isWifiDefault.launchIn(this)
 
             val wifiInfo = mock<WifiInfo>().apply { whenever(this.ssid).thenReturn(SSID) }
@@ -268,7 +262,7 @@
     /** Regression test for b/266628069. */
     @Test
     fun isWifiDefault_transportInfoIsNotWifi_andNoWifiTransport_false() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val job = underTest.isWifiDefault.launchIn(this)
 
             val transportInfo =
@@ -294,7 +288,7 @@
     /** Regression test for b/266628069. */
     @Test
     fun isWifiDefault_transportInfoIsNotWifi_butHasWifiTransport_true() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val job = underTest.isWifiDefault.launchIn(this)
 
             val transportInfo =
@@ -319,7 +313,7 @@
 
     @Test
     fun isWifiDefault_carrierMergedViaCellular_isTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val job = underTest.isWifiDefault.launchIn(this)
 
             val carrierMergedInfo =
@@ -341,7 +335,7 @@
 
     @Test
     fun isWifiDefault_carrierMergedViaCellular_withVcnTransport_isTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val job = underTest.isWifiDefault.launchIn(this)
 
             val capabilities =
@@ -360,7 +354,7 @@
 
     @Test
     fun isWifiDefault_carrierMergedViaWifi_isTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val job = underTest.isWifiDefault.launchIn(this)
 
             val carrierMergedInfo =
@@ -382,7 +376,7 @@
 
     @Test
     fun isWifiDefault_carrierMergedViaWifi_withVcnTransport_isTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val job = underTest.isWifiDefault.launchIn(this)
 
             val capabilities =
@@ -400,8 +394,8 @@
         }
 
     @Test
-    fun wifiNetwork_cellularAndWifiTransports_usesCellular_isTrue() =
-        runBlocking(IMMEDIATE) {
+    fun isWifiDefault_cellularAndWifiTransports_usesCellular_isTrue() =
+        testScope.runTest {
             val job = underTest.isWifiDefault.launchIn(this)
 
             val capabilities =
@@ -420,7 +414,7 @@
 
     @Test
     fun isWifiDefault_cellularNotVcnNetwork_isFalse() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val job = underTest.isWifiDefault.launchIn(this)
 
             val capabilities =
@@ -437,8 +431,77 @@
         }
 
     @Test
+    fun isWifiDefault_isCarrierMergedViaUnderlyingWifi_isTrue() =
+        testScope.runTest {
+            val job = underTest.isWifiDefault.launchIn(this)
+
+            val underlyingNetwork = mock<Network>()
+            val carrierMergedInfo =
+                mock<WifiInfo>().apply {
+                    mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+                }
+            val underlyingWifiCapabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+                }
+            whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+                .thenReturn(underlyingWifiCapabilities)
+
+            // WHEN the main capabilities have an underlying carrier merged network via WIFI
+            // transport and WifiInfo
+            val mainCapabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(null)
+                    whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+                }
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+            // THEN the wifi network is carrier merged, so wifi is default
+            assertThat(underTest.isWifiDefault.value).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isWifiDefault_isCarrierMergedViaUnderlyingCellular_isTrue() =
+        testScope.runTest {
+            val job = underTest.isWifiDefault.launchIn(this)
+
+            val underlyingCarrierMergedNetwork = mock<Network>()
+            val carrierMergedInfo =
+                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+            val underlyingCapabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+                }
+            whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+                .thenReturn(underlyingCapabilities)
+
+            // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
+            // transport and VcnTransportInfo
+            val mainCapabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(null)
+                    whenever(it.underlyingNetworks)
+                        .thenReturn(listOf(underlyingCarrierMergedNetwork))
+                }
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+            // THEN the wifi network is carrier merged, so wifi is default
+            assertThat(underTest.isWifiDefault.value).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
     fun isWifiDefault_wifiNetworkLost_isFalse() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val job = underTest.isWifiDefault.launchIn(this)
 
             // First, add a network
@@ -457,7 +520,7 @@
 
     @Test
     fun wifiNetwork_initiallyGetsDefault() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -468,7 +531,7 @@
 
     @Test
     fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -492,7 +555,7 @@
 
     @Test
     fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -511,8 +574,83 @@
         }
 
     @Test
+    fun wifiNetwork_isCarrierMergedViaUnderlyingWifi_flowHasCarrierMerged() =
+        testScope.runTest {
+            var latest: WifiNetworkModel? = null
+            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+
+            val underlyingNetwork = mock<Network>()
+            val carrierMergedInfo =
+                mock<WifiInfo>().apply {
+                    whenever(this.isCarrierMerged).thenReturn(true)
+                    whenever(this.isPrimary).thenReturn(true)
+                }
+            val underlyingWifiCapabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+                }
+            whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+                .thenReturn(underlyingWifiCapabilities)
+
+            // WHEN the main capabilities have an underlying carrier merged network via WIFI
+            // transport and WifiInfo
+            val mainCapabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(null)
+                    whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+                }
+
+            getNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+            // THEN the wifi network is carrier merged
+            assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun wifiNetwork_isCarrierMergedViaUnderlyingCellular_flowHasCarrierMerged() =
+        testScope.runTest {
+            var latest: WifiNetworkModel? = null
+            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+
+            val underlyingCarrierMergedNetwork = mock<Network>()
+            val carrierMergedInfo =
+                mock<WifiInfo>().apply {
+                    whenever(this.isCarrierMerged).thenReturn(true)
+                    whenever(this.isPrimary).thenReturn(true)
+                }
+            val underlyingCapabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+                }
+            whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+                .thenReturn(underlyingCapabilities)
+
+            // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
+            // transport and VcnTransportInfo
+            val mainCapabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(null)
+                    whenever(it.underlyingNetworks)
+                        .thenReturn(listOf(underlyingCarrierMergedNetwork))
+                }
+
+            getNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+            // THEN the wifi network is carrier merged
+            assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
     fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -536,7 +674,7 @@
 
     @Test
     fun wifiNetwork_isCarrierMerged_getsCorrectValues() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -571,7 +709,7 @@
 
     @Test
     fun wifiNetwork_notValidated_networkNotValidated() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -588,7 +726,7 @@
 
     @Test
     fun wifiNetwork_validated_networkValidated() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -605,7 +743,7 @@
 
     @Test
     fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -626,7 +764,7 @@
     /** Regression test for b/266628069. */
     @Test
     fun wifiNetwork_transportInfoIsNotWifi_flowHasNoNetwork() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -645,7 +783,7 @@
 
     @Test
     fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -667,7 +805,7 @@
 
     @Test
     fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -691,7 +829,7 @@
 
     @Test
     fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -710,7 +848,7 @@
 
     @Test
     fun wifiNetwork_cellularAndWifiTransports_usesCellular() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -733,7 +871,7 @@
 
     @Test
     fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -766,7 +904,7 @@
 
     @Test
     fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -799,7 +937,7 @@
 
     @Test
     fun wifiNetwork_newNetworkCapabilities_flowHasNewData() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -842,7 +980,7 @@
 
     @Test
     fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -857,7 +995,7 @@
 
     @Test
     fun wifiNetwork_currentNetworkLost_flowHasNoNetwork() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -876,7 +1014,7 @@
 
     @Test
     fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -899,7 +1037,7 @@
 
     @Test
     fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
 
@@ -925,7 +1063,7 @@
     /** Regression test for b/244173280. */
     @Test
     fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest1: WifiNetworkModel? = null
             val job1 = underTest.wifiNetwork.onEach { latest1 = it }.launchIn(this)
 
@@ -952,8 +1090,151 @@
         }
 
     @Test
+    fun isWifiConnectedWithValidSsid_inactiveNetwork_false() =
+        testScope.runTest {
+            val job = underTest.wifiNetwork.launchIn(this)
+
+            val wifiInfo =
+                mock<WifiInfo>().apply {
+                    whenever(this.ssid).thenReturn(SSID)
+                    // A non-primary network is inactive
+                    whenever(this.isPrimary).thenReturn(false)
+                }
+
+            getNetworkCallback()
+                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isWifiConnectedWithValidSsid_carrierMergedNetwork_false() =
+        testScope.runTest {
+            val job = underTest.wifiNetwork.launchIn(this)
+
+            val wifiInfo =
+                mock<WifiInfo>().apply {
+                    whenever(this.isPrimary).thenReturn(true)
+                    whenever(this.isCarrierMerged).thenReturn(true)
+                }
+
+            getNetworkCallback()
+                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isWifiConnectedWithValidSsid_invalidNetwork_false() =
+        testScope.runTest {
+            val job = underTest.wifiNetwork.launchIn(this)
+
+            val wifiInfo =
+                mock<WifiInfo>().apply {
+                    whenever(this.isPrimary).thenReturn(true)
+                    whenever(this.isCarrierMerged).thenReturn(true)
+                    whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
+                }
+
+            getNetworkCallback()
+                .onCapabilitiesChanged(
+                    NETWORK,
+                    createWifiNetworkCapabilities(wifiInfo),
+                )
+
+            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() =
+        testScope.runTest {
+            val job = underTest.wifiNetwork.launchIn(this)
+
+            val wifiInfo =
+                mock<WifiInfo>().apply {
+                    whenever(this.isPrimary).thenReturn(true)
+                    whenever(this.ssid).thenReturn(null)
+                }
+
+            getNetworkCallback()
+                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() =
+        testScope.runTest {
+            val job = underTest.wifiNetwork.launchIn(this)
+
+            val wifiInfo =
+                mock<WifiInfo>().apply {
+                    whenever(this.isPrimary).thenReturn(true)
+                    whenever(this.ssid).thenReturn(UNKNOWN_SSID)
+                }
+
+            getNetworkCallback()
+                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() =
+        testScope.runTest {
+            val job = underTest.wifiNetwork.launchIn(this)
+
+            val wifiInfo =
+                mock<WifiInfo>().apply {
+                    whenever(this.isPrimary).thenReturn(true)
+                    whenever(this.ssid).thenReturn("FakeSsid")
+                }
+
+            getNetworkCallback()
+                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+            assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isWifiConnectedWithValidSsid_activeToInactive_trueToFalse() =
+        testScope.runTest {
+            val job = underTest.wifiNetwork.launchIn(this)
+
+            // Start with active
+            val wifiInfo =
+                mock<WifiInfo>().apply {
+                    whenever(this.isPrimary).thenReturn(true)
+                    whenever(this.ssid).thenReturn("FakeSsid")
+                }
+            getNetworkCallback()
+                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+            assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
+
+            // WHEN the network is lost
+            getNetworkCallback().onLost(NETWORK)
+
+            // THEN the isWifiConnected updates
+            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
     fun wifiActivity_callbackGivesNone_activityFlowHasNone() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataActivityModel? = null
             val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
 
@@ -967,7 +1248,7 @@
 
     @Test
     fun wifiActivity_callbackGivesIn_activityFlowHasIn() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataActivityModel? = null
             val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
 
@@ -981,7 +1262,7 @@
 
     @Test
     fun wifiActivity_callbackGivesOut_activityFlowHasOut() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataActivityModel? = null
             val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
 
@@ -995,7 +1276,7 @@
 
     @Test
     fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataActivityModel? = null
             val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
 
@@ -1015,7 +1296,7 @@
             logger,
             tableLogger,
             executor,
-            scope,
+            testScope.backgroundScope,
             wifiManager,
         )
     }
@@ -1060,5 +1341,3 @@
             }
     }
 }
-
-private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
index ab4e93c..4e0c309 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.shared.model
 
+import android.net.wifi.WifiManager.UNKNOWN_SSID
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -50,6 +51,42 @@
         WifiNetworkModel.CarrierMerged(NETWORK_ID, INVALID_SUBSCRIPTION_ID, 1)
     }
 
+    @Test
+    fun active_hasValidSsid_nullSsid_false() {
+        val network =
+            WifiNetworkModel.Active(
+                NETWORK_ID,
+                level = MAX_VALID_LEVEL,
+                ssid = null,
+            )
+
+        assertThat(network.hasValidSsid()).isFalse()
+    }
+
+    @Test
+    fun active_hasValidSsid_unknownSsid_false() {
+        val network =
+            WifiNetworkModel.Active(
+                NETWORK_ID,
+                level = MAX_VALID_LEVEL,
+                ssid = UNKNOWN_SSID,
+            )
+
+        assertThat(network.hasValidSsid()).isFalse()
+    }
+
+    @Test
+    fun active_hasValidSsid_validSsid_true() {
+        val network =
+            WifiNetworkModel.Active(
+                NETWORK_ID,
+                level = MAX_VALID_LEVEL,
+                ssid = "FakeSsid",
+            )
+
+        assertThat(network.hasValidSsid()).isTrue()
+    }
+
     // Non-exhaustive logDiffs test -- just want to make sure the logging logic isn't totally broken
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 1eee08c..91c88ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -21,6 +21,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
 
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
@@ -167,8 +168,10 @@
         mBatteryController.setPowerSaveMode(false, mView);
 
         StaticInOrder inOrder = inOrder(staticMockMarker(BatterySaverUtils.class));
-        inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true));
-        inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true));
+        inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true,
+                SAVER_ENABLED_QS));
+        inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true,
+                SAVER_ENABLED_QS));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 01e94ba..391c8ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -62,7 +62,7 @@
 import android.window.WindowOnBackInvokedDispatcher;
 
 import androidx.annotation.NonNull;
-import androidx.core.animation.AnimatorTestRule2;
+import androidx.core.animation.AnimatorTestRule;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
@@ -110,7 +110,7 @@
     private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
 
     @ClassRule
-    public static AnimatorTestRule2 mAnimatorTestRule = new AnimatorTestRule2();
+    public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
 
     @Before
     public void setUp() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 28bdca9..e824565 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -85,6 +85,9 @@
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.launcher3.icons.BubbleBadgeIconFactory;
+import com.android.launcher3.icons.BubbleIconFactory;
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -127,11 +130,9 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.bubbles.Bubble;
-import com.android.wm.shell.bubbles.BubbleBadgeIconFactory;
 import com.android.wm.shell.bubbles.BubbleData;
 import com.android.wm.shell.bubbles.BubbleDataRepository;
 import com.android.wm.shell.bubbles.BubbleEntry;
-import com.android.wm.shell.bubbles.BubbleIconFactory;
 import com.android.wm.shell.bubbles.BubbleLogger;
 import com.android.wm.shell.bubbles.BubbleStackView;
 import com.android.wm.shell.bubbles.BubbleViewInfoTask;
@@ -1225,8 +1226,13 @@
         BubbleViewInfoTask.BubbleViewInfo info = BubbleViewInfoTask.BubbleViewInfo.populate(context,
                 mBubbleController,
                 mBubbleController.getStackView(),
-                new BubbleIconFactory(mContext),
-                new BubbleBadgeIconFactory(mContext),
+                new BubbleIconFactory(mContext,
+                        mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size)),
+                new BubbleBadgeIconFactory(mContext,
+                        mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
+                        mContext.getResources().getColor(R.color.important_conversation),
+                        mContext.getResources().getDimensionPixelSize(
+                                com.android.internal.R.dimen.importance_ring_stroke_width)),
                 bubble,
                 true /* skipInflation */);
         verify(userContext, times(1)).getPackageManager();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
new file mode 100644
index 0000000..c08ecd0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
+
+    override val isAuthenticated = MutableStateFlow(false)
+    override val canRunFaceAuth = MutableStateFlow(false)
+    private val _authenticationStatus = MutableStateFlow<AuthenticationStatus?>(null)
+    override val authenticationStatus: Flow<AuthenticationStatus> =
+        _authenticationStatus.filterNotNull()
+    fun setAuthenticationStatus(status: AuthenticationStatus) {
+        _authenticationStatus.value = status
+    }
+    private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
+    override val detectionStatus: Flow<DetectionStatus>
+        get() = _detectionStatus.filterNotNull()
+    fun setDetectionStatus(status: DetectionStatus) {
+        _detectionStatus.value = status
+    }
+    override val isLockedOut = MutableStateFlow(false)
+    private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null)
+    val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
+        _runningAuthRequest.asStateFlow()
+    override val isAuthRunning = _runningAuthRequest.map { it != null }
+    override val isBypassEnabled = MutableStateFlow(false)
+
+    override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
+        _runningAuthRequest.value = uiEvent to fallbackToDetection
+    }
+
+    override fun cancel() {
+        _runningAuthRequest.value = null
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt
new file mode 100644
index 0000000..9383a0a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.content.Context
+import com.android.systemui.plugins.qs.QSFactory
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.qs.QSTileView
+
+class FakeQSFactory(private val tileCreator: (String) -> QSTile?) : QSFactory {
+    override fun createTile(tileSpec: String): QSTile? {
+        return tileCreator(tileSpec)
+    }
+
+    override fun createTileView(
+        context: Context?,
+        tile: QSTile?,
+        collapsedView: Boolean
+    ): QSTileView {
+        throw NotImplementedError("Not implemented")
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeCustomTileAddedRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeCustomTileAddedRepository.kt
new file mode 100644
index 0000000..7771304
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeCustomTileAddedRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.content.ComponentName
+
+class FakeCustomTileAddedRepository : CustomTileAddedRepository {
+
+    private val tileAddedRegistry = mutableSetOf<Pair<Int, ComponentName>>()
+
+    override fun isTileAdded(componentName: ComponentName, userId: Int): Boolean {
+        return (userId to componentName) in tileAddedRegistry
+    }
+
+    override fun setTileAdded(componentName: ComponentName, userId: Int, added: Boolean) {
+        if (added) {
+            tileAddedRegistry.add(userId to componentName)
+        } else {
+            tileAddedRegistry.remove(userId to componentName)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
new file mode 100644
index 0000000..2865710
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.util.Log
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeTileSpecRepository : TileSpecRepository {
+
+    private val tilesPerUser = mutableMapOf<Int, MutableStateFlow<List<TileSpec>>>()
+
+    override fun tilesSpecs(userId: Int): Flow<List<TileSpec>> {
+        return getFlow(userId).asStateFlow().also { Log.d("Fabian", "Retrieving flow for $userId") }
+    }
+
+    override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) {
+        if (tile == TileSpec.Invalid) return
+        with(getFlow(userId)) {
+            value =
+                value.toMutableList().apply {
+                    if (position == POSITION_AT_END) {
+                        add(tile)
+                    } else {
+                        add(position, tile)
+                    }
+                }
+        }
+    }
+
+    override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) {
+        with(getFlow(userId)) {
+            value =
+                value.toMutableList().apply { removeAll(tiles.filter { it != TileSpec.Invalid }) }
+        }
+    }
+
+    override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) {
+        getFlow(userId).value = tiles.filter { it != TileSpec.Invalid }
+    }
+
+    private fun getFlow(userId: Int): MutableStateFlow<List<TileSpec>> =
+        tilesPerUser.getOrPut(userId) { MutableStateFlow(emptyList()) }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
index 380c1fc..156d2fc 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
@@ -25,14 +25,18 @@
  * It could be used when no activity context is available
  * TODO(b/232369816): use Jetpack WM library when non-activity contexts supported b/169740873
  */
-class ScreenSizeFoldProvider(private val context: Context) : FoldProvider {
+class ScreenSizeFoldProvider(context: Context) : FoldProvider {
 
+    private var isFolded: Boolean = false
     private var callbacks: MutableList<FoldCallback> = arrayListOf()
-    private var lastWidth: Int = 0
+
+    init {
+        onConfigurationChange(context.resources.configuration)
+    }
 
     override fun registerCallback(callback: FoldCallback, executor: Executor) {
         callbacks += callback
-        onConfigurationChange(context.resources.configuration)
+        callback.onFoldUpdated(isFolded)
     }
 
     override fun unregisterCallback(callback: FoldCallback) {
@@ -40,16 +44,14 @@
     }
 
     fun onConfigurationChange(newConfig: Configuration) {
-        if (lastWidth == newConfig.smallestScreenWidthDp) {
+        val newIsFolded =
+                newConfig.smallestScreenWidthDp < INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP
+        if (newIsFolded == isFolded) {
             return
         }
 
-        if (newConfig.smallestScreenWidthDp > INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP) {
-            callbacks.forEach { it.onFoldUpdated(false) }
-        } else {
-            callbacks.forEach { it.onFoldUpdated(true) }
-        }
-        lastWidth = newConfig.smallestScreenWidthDp
+        isFolded = newIsFolded
+        callbacks.forEach { it.onFoldUpdated(isFolded) }
     }
 }
 
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index a633a5e..46001a7 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -256,6 +256,12 @@
             }
         }
 
+        override fun markScreenAsTurnedOn() {
+            if (!isFolded) {
+                isUnfoldHandled = true
+            }
+        }
+
         override fun onScreenTurningOn() {
             isScreenOn = true
             updateHingeAngleProviderState()
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
index f09b53d..2ee2940 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
@@ -35,5 +35,12 @@
          * Called when the screen is starting to be turned on.
          */
         fun onScreenTurningOn()
+
+        /**
+         * Called when the screen is already turned on but it happened before the creation
+         * of the unfold progress provider, so we won't play the actual animation but we treat
+         * the current state of the screen as 'turned on'
+         */
+        fun markScreenAsTurnedOn()
     }
 }
diff --git a/services/Android.bp b/services/Android.bp
index 6e6c553..b0a0e5e 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -112,6 +112,7 @@
         ":services.searchui-sources",
         ":services.selectiontoolbar-sources",
         ":services.smartspace-sources",
+        ":services.soundtrigger-sources",
         ":services.systemcaptions-sources",
         ":services.translation-sources",
         ":services.texttospeech-sources",
@@ -169,6 +170,7 @@
         "services.searchui",
         "services.selectiontoolbar",
         "services.smartspace",
+        "services.soundtrigger",
         "services.systemcaptions",
         "services.translation",
         "services.texttospeech",
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 2188b99..2f3e4c0 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -24,6 +24,7 @@
 import android.app.UiAutomation;
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
@@ -34,6 +35,7 @@
 import android.view.accessibility.AccessibilityEvent;
 
 import com.android.internal.util.DumpUtils;
+import com.android.server.utils.Slogf;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.io.FileDescriptor;
@@ -51,12 +53,8 @@
 
     private UiAutomationService mUiAutomationService;
 
-    private AccessibilityServiceInfo mUiAutomationServiceInfo;
-
     private AbstractAccessibilityServiceConnection.SystemSupport mSystemSupport;
 
-    private AccessibilityTrace mTrace;
-
     private int mUiAutomationFlags;
 
     UiAutomationManager(Object lock) {
@@ -97,9 +95,10 @@
             WindowManagerInternal windowManagerInternal,
             SystemActionPerformer systemActionPerformer,
             AccessibilityWindowManager awm, int flags) {
+        accessibilityServiceInfo.setComponentName(COMPONENT_NAME);
+        Slogf.i(LOG_TAG, "Registering UiTestAutomationService (id=%s) when called by user %d",
+                accessibilityServiceInfo.getId(), Binder.getCallingUserHandle().getIdentifier());
         synchronized (mLock) {
-            accessibilityServiceInfo.setComponentName(COMPONENT_NAME);
-
             if (mUiAutomationService != null) {
                 throw new IllegalStateException(
                         "UiAutomationService " + mUiAutomationService.mServiceInterface
@@ -116,7 +115,6 @@
 
             mUiAutomationFlags = flags;
             mSystemSupport = systemSupport;
-            mTrace = trace;
             // Ignore registering UiAutomation if it is not allowed to use the accessibility
             // subsystem.
             if (!useAccessibility()) {
@@ -126,7 +124,6 @@
                     mainHandler, mLock, securityPolicy, systemSupport, trace, windowManagerInternal,
                     systemActionPerformer, awm);
             mUiAutomationServiceOwner = owner;
-            mUiAutomationServiceInfo = accessibilityServiceInfo;
             mUiAutomationService.mServiceInterface = serviceClient;
             try {
                 mUiAutomationService.mServiceInterface.asBinder().linkToDeath(mUiAutomationService,
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 8d039fc..2a964b8 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2073,15 +2073,14 @@
     @Override
     public void onSaveRequestSuccess(@NonNull String servicePackageName,
             @Nullable IntentSender intentSender) {
-        // Log onSaveRequest result.
-        mSaveEventLogger.maybeSetIsSaved(true);
-        final long saveRequestFinishTimestamp = SystemClock.elapsedRealtime() - mLatencyBaseTime;
-        mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
-        mSaveEventLogger.logAndEndEvent();
-
         synchronized (mLock) {
             mSessionFlags.mShowingSaveUi = false;
-
+            // Log onSaveRequest result.
+            mSaveEventLogger.maybeSetIsSaved(true);
+            final long saveRequestFinishTimestamp =
+                SystemClock.elapsedRealtime() - mLatencyBaseTime;
+            mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
+            mSaveEventLogger.logAndEndEvent();
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: "
                         + id + " destroyed");
@@ -2108,14 +2107,13 @@
             @NonNull String servicePackageName) {
         boolean showMessage = !TextUtils.isEmpty(message);
 
-        // Log onSaveRequest result.
-        final long saveRequestFinishTimestamp = SystemClock.elapsedRealtime() - mLatencyBaseTime;
-        mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
-        mSaveEventLogger.logAndEndEvent();
-
         synchronized (mLock) {
             mSessionFlags.mShowingSaveUi = false;
-
+            // Log onSaveRequest result.
+            final long saveRequestFinishTimestamp =
+                SystemClock.elapsedRealtime() - mLatencyBaseTime;
+            mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
+            mSaveEventLogger.logAndEndEvent();
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: "
                         + id + " destroyed");
@@ -2228,8 +2226,8 @@
     // AutoFillUiCallback
     @Override
     public void save() {
-        mSaveEventLogger.maybeSetSaveButtonClicked(true);
         synchronized (mLock) {
+            mSaveEventLogger.maybeSetSaveButtonClicked(true);
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#save() rejected - session: "
                         + id + " destroyed");
@@ -2247,10 +2245,9 @@
     // AutoFillUiCallback
     @Override
     public void cancelSave() {
-        mSaveEventLogger.maybeSetDialogDismissed(true);
         synchronized (mLock) {
             mSessionFlags.mShowingSaveUi = false;
-
+            mSaveEventLogger.maybeSetDialogDismissed(true);
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#cancelSave() rejected - session: "
                         + id + " destroyed");
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 0630956..87b82bd 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -17,7 +17,12 @@
 
 package com.android.server.companion;
 
+import static android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES;
+import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
+import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
+import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
 import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Process.SYSTEM_UID;
@@ -32,7 +37,6 @@
 import static com.android.server.companion.PackageUtils.getPackageInfo;
 import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice;
 import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
-import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageCompanionDevice;
 import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr;
 import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
 import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks;
@@ -42,6 +46,7 @@
 import static java.util.concurrent.TimeUnit.DAYS;
 import static java.util.concurrent.TimeUnit.MINUTES;
 
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -574,29 +579,33 @@
         }
 
         @Override
+        @EnforcePermission(MANAGE_COMPANION_DEVICES)
         public List<AssociationInfo> getAllAssociationsForUser(int userId) throws RemoteException {
+            getAllAssociationsForUser_enforcePermission();
+
             enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId);
-            enforceCallerCanManageCompanionDevice(getContext(), "getAllAssociationsForUser");
 
             return mAssociationStore.getAssociationsForUser(userId);
         }
 
         @Override
+        @EnforcePermission(MANAGE_COMPANION_DEVICES)
         public void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener,
                 int userId) {
+            addOnAssociationsChangedListener_enforcePermission();
+
             enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId);
-            enforceCallerCanManageCompanionDevice(getContext(),
-                    "addOnAssociationsChangedListener");
 
             mListeners.register(listener, userId);
         }
 
         @Override
+        @EnforcePermission(MANAGE_COMPANION_DEVICES)
         public void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener,
                 int userId) {
+            removeOnAssociationsChangedListener_enforcePermission();
+
             enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId);
-            enforceCallerCanManageCompanionDevice(
-                    getContext(), "removeOnAssociationsChangedListener");
 
             mListeners.unregister(listener);
         }
@@ -632,6 +641,10 @@
             mTransportManager.removeListener(messageType, listener);
         }
 
+        /**
+         * @deprecated use {@link #disassociate(int)} instead
+         */
+        @Deprecated
         @Override
         public void legacyDisassociate(String deviceMacAddress, String packageName, int userId) {
             Log.i(TAG, "legacyDisassociate() pkg=u" + userId + "/" + packageName
@@ -687,8 +700,8 @@
             return nm.isNotificationListenerAccessGranted(component);
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
         @Override
+        @EnforcePermission(MANAGE_COMPANION_DEVICES)
         public boolean isDeviceAssociatedForWifiConnection(String packageName, String macAddress,
                 int userId) {
             isDeviceAssociatedForWifiConnection_enforcePermission();
@@ -705,15 +718,19 @@
         }
 
         @Override
+        @EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
         public void registerDevicePresenceListenerService(String deviceAddress,
                 String callingPackage, int userId) throws RemoteException {
+            registerDevicePresenceListenerService_enforcePermission();
             // TODO: take the userId into account.
             registerDevicePresenceListenerActive(callingPackage, deviceAddress, true);
         }
 
         @Override
+        @EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
         public void unregisterDevicePresenceListenerService(String deviceAddress,
                 String callingPackage, int userId) throws RemoteException {
+            unregisterDevicePresenceListenerService_enforcePermission();
             // TODO: take the userId into account.
             registerDevicePresenceListenerActive(callingPackage, deviceAddress, false);
         }
@@ -733,14 +750,20 @@
         }
 
         @Override
+        @EnforcePermission(DELIVER_COMPANION_MESSAGES)
         public void attachSystemDataTransport(String packageName, int userId, int associationId,
                 ParcelFileDescriptor fd) {
+            attachSystemDataTransport_enforcePermission();
+
             getAssociationWithCallerChecks(associationId);
             mTransportManager.attachSystemDataTransport(packageName, userId, associationId, fd);
         }
 
         @Override
+        @EnforcePermission(DELIVER_COMPANION_MESSAGES)
         public void detachSystemDataTransport(String packageName, int userId, int associationId) {
+            detachSystemDataTransport_enforcePermission();
+
             getAssociationWithCallerChecks(associationId);
             mTransportManager.detachSystemDataTransport(packageName, userId, associationId);
         }
@@ -809,9 +832,6 @@
                         + " deviceAddress=" + deviceAddress);
             }
 
-            getContext().enforceCallingOrSelfPermission(
-                    android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE,
-                    "[un]registerDevicePresenceListenerService");
             final int userId = getCallingUserId();
             enforceCallerIsSystemOr(userId, packageName);
 
@@ -854,17 +874,17 @@
         }
 
         @Override
+        @EnforcePermission(ASSOCIATE_COMPANION_DEVICES)
         public void createAssociation(String packageName, String macAddress, int userId,
                 byte[] certificate) {
+            createAssociation_enforcePermission();
+
             if (!getContext().getPackageManager().hasSigningCertificate(
                     packageName, certificate, CERT_INPUT_SHA256)) {
                 Slog.e(TAG, "Given certificate doesn't match the package certificate.");
                 return;
             }
 
-            getContext().enforceCallingOrSelfPermission(
-                    android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES, "createAssociation");
-
             final MacAddress macAddressObj = MacAddress.fromString(macAddress);
             createNewAssociation(userId, packageName, macAddressObj, null, null, false);
         }
@@ -897,12 +917,9 @@
         public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
                 String[] args, ShellCallback callback, ResultReceiver resultReceiver)
                 throws RemoteException {
-            enforceCallerCanManageCompanionDevice(getContext(), "onShellCommand");
-            final CompanionDeviceShellCommand cmd = new CompanionDeviceShellCommand(
-                    CompanionDeviceManagerService.this,
-                    mAssociationStore,
-                    mDevicePresenceMonitor);
-            cmd.exec(this, in, out, err, args, callback, resultReceiver);
+            new CompanionDeviceShellCommand(CompanionDeviceManagerService.this, mAssociationStore,
+                    mDevicePresenceMonitor)
+                    .exec(this, in, out, err, args, callback, resultReceiver);
         }
 
         @Override
@@ -1065,6 +1082,10 @@
             // No role was granted to for this association, there is nothing else we need to here.
             return true;
         }
+        // Do not need to remove the system role since it was pre-granted by the system.
+        if (deviceProfile.equals(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION)) {
+            return true;
+        }
 
         // Check if the applications is associated with another devices with the profile. If so,
         // it should remain the role holder.
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java
index 0ff3fb7..f4e14df 100644
--- a/services/companion/java/com/android/server/companion/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java
@@ -163,13 +163,6 @@
         return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED;
     }
 
-    static void enforceCallerCanManageCompanionDevice(@NonNull Context context,
-            @Nullable String message) {
-        if (getCallingUid() == SYSTEM_UID) return;
-
-        context.enforceCallingPermission(MANAGE_COMPANION_DEVICES, message);
-    }
-
     static void enforceCallerCanManageAssociationsForPackage(@NonNull Context context,
             @UserIdInt int userId, @NonNull String packageName,
             @Nullable String actionDescription) {
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 092eb4e..6d95f7c 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -16,8 +16,6 @@
 
 package com.android.server.companion.transport;
 
-import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
-
 import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE;
 import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PLATFORM_INFO;
 
@@ -170,8 +168,6 @@
      * third-party companion apps.
      */
     private void enforceCallerCanTransportSystemData(String packageName, int userId) {
-        mContext.enforceCallingOrSelfPermission(DELIVER_COMPANION_MESSAGES, TAG);
-
         try {
             final ApplicationInfo info = mContext.getPackageManager().getApplicationInfoAsUser(
                     packageName, 0, userId);
@@ -301,25 +297,31 @@
 
         int sdk = Build.VERSION.SDK_INT;
         String release = Build.VERSION.RELEASE;
-        if (Build.isDebuggable()) {
-            // Debug builds cannot pass attestation verification. Use hardcoded key instead.
+
+        if (sdk < SECURE_CHANNEL_AVAILABLE_SDK || remoteSdk < SECURE_CHANNEL_AVAILABLE_SDK) {
+            // If either device is Android T or below, use raw channel
+            // TODO: depending on the release version, either
+            //       1) using a RawTransport for old T versions
+            //       2) or an Ukey2 handshaked transport for UKey2 backported T versions
+            Slog.d(TAG, "Secure channel is not supported. Using raw transport");
+            transport = new RawTransport(transport.getAssociationId(), transport.getFd(), mContext);
+        } else if (Build.isDebuggable()) {
+            // If device is debug build, use hardcoded test key for authentication
             Slog.d(TAG, "Creating an unauthenticated secure channel");
             final byte[] testKey = "CDM".getBytes(StandardCharsets.UTF_8);
             transport = new SecureTransport(transport.getAssociationId(), transport.getFd(),
                     mContext, testKey, null);
-        } else if (remoteSdk == NON_ANDROID) {
+        } else if (sdk == NON_ANDROID || remoteSdk == NON_ANDROID) {
+            // If either device is not Android, then use app-specific pre-shared key
             // TODO: pass in a real preSharedKey
+            Slog.d(TAG, "Creating a PSK-authenticated secure channel");
             transport = new SecureTransport(transport.getAssociationId(), transport.getFd(),
                     mContext, new byte[0], null);
-        } else if (sdk >= SECURE_CHANNEL_AVAILABLE_SDK
-                && remoteSdk >= SECURE_CHANNEL_AVAILABLE_SDK) {
-            Slog.i(TAG, "Creating a secure channel");
+        } else {
+            // If none of the above applies, then use secure channel with attestation verification
+            Slog.d(TAG, "Creating a secure channel");
             transport = new SecureTransport(transport.getAssociationId(), transport.getFd(),
                     mContext);
-        } else {
-            // TODO: depending on the release version, either
-            //       1) using a RawTransport for old T versions
-            //       2) or an Ukey2 handshaked transport for UKey2 backported T versions
         }
         addMessageListenersToTransport(transport);
         transport.start();
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 14247c5..ceb45be 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -174,7 +174,6 @@
         "android.hardware.configstore-V1.1-java",
         "android.hardware.ir-V1-java",
         "android.hardware.rebootescrow-V1-java",
-        "android.hardware.soundtrigger-V2.3-java",
         "android.hardware.power.stats-V2-java",
         "android.hardware.power-V4-java",
         "android.hidl.manager-V1.2-java",
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index c5eb25b..3fd6fe8 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -26,8 +26,8 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.IActivityManager;
-import android.app.IUidObserver;
 import android.app.SearchManager;
+import android.app.UidObserver;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -360,35 +360,18 @@
 
     private void registerUidListener() {
         try {
-            mAm.registerUidObserver(new IUidObserver.Stub() {
+            mAm.registerUidObserver(new UidObserver() {
                 @Override
-                public void onUidGone(int uid, boolean disabled) throws RemoteException {
+                public void onUidGone(int uid, boolean disabled) {
                     mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
                             PinnerService::handleUidGone, PinnerService.this, uid));
                 }
 
                 @Override
-                public void onUidActive(int uid) throws RemoteException {
+                public void onUidActive(int uid)  {
                     mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
                             PinnerService::handleUidActive, PinnerService.this, uid));
                 }
-
-                @Override
-                public void onUidIdle(int uid, boolean disabled) throws RemoteException {
-                }
-
-                @Override
-                public void onUidStateChanged(int uid, int procState, long procStateSeq,
-                        int capability) throws RemoteException {
-                }
-
-                @Override
-                public void onUidCachedChanged(int uid, boolean cached) throws RemoteException {
-                }
-
-                @Override
-                public void onUidProcAdjChanged(int uid) throws RemoteException {
-                }
             }, UID_OBSERVER_GONE | UID_OBSERVER_ACTIVE, 0, null);
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to register uid observer", e);
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 46d019b..6e2e5f7 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -107,7 +107,7 @@
     static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG =
             "namespace_to_package_mapping";
     @VisibleForTesting
-    static final long FACTORY_RESET_THROTTLE_DURATION_MS = TimeUnit.MINUTES.toMillis(10);
+    static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 10;
 
     private static final String NAME = "rescue-party-observer";
 
@@ -117,6 +117,8 @@
             "persist.device_config.configuration.disable_rescue_party";
     private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
             "persist.device_config.configuration.disable_rescue_party_factory_reset";
+    private static final String PROP_THROTTLE_DURATION_MIN_FLAG =
+            "persist.device_config.configuration.rescue_party_throttle_duration_min";
 
     private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
             | ApplicationInfo.FLAG_SYSTEM;
@@ -722,7 +724,9 @@
         private boolean shouldThrottleReboot() {
             Long lastResetTime = SystemProperties.getLong(PROP_LAST_FACTORY_RESET_TIME_MS, 0);
             long now = System.currentTimeMillis();
-            return now < lastResetTime + FACTORY_RESET_THROTTLE_DURATION_MS;
+            long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG,
+                    DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN);
+            return now < lastResetTime + TimeUnit.MINUTES.toMillis(throttleDurationMin);
         }
 
         private boolean isPersistentSystemApp(@NonNull String packageName) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/core/java/com/android/server/SoundTriggerInternal.java
similarity index 97%
rename from services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
rename to services/core/java/com/android/server/SoundTriggerInternal.java
index cc398d9..e6c1750 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
+++ b/services/core/java/com/android/server/SoundTriggerInternal.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.soundtrigger;
+package com.android.server;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -29,15 +29,13 @@
 import android.media.permission.Identity;
 import android.os.IBinder;
 
-import com.android.server.voiceinteraction.VoiceInteractionManagerService;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.List;
 
 /**
  * Provides a local service for managing voice-related recoginition models. This is primarily used
- * by the {@link VoiceInteractionManagerService}.
+ * by the {@code VoiceInteractionManagerService}.
  */
 public interface SoundTriggerInternal {
     /**
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 916c5ca..16ccdb9 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -26,6 +26,17 @@
 import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
 import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
 import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DEPRECATED;
 import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DISABLED;
 import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_OK;
@@ -88,6 +99,7 @@
 import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED;
 import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER;
 import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT;
+import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT;
 import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED;
 import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
 import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED;
@@ -116,6 +128,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityManagerInternal.ServiceNotificationPolicy;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
@@ -1145,7 +1158,7 @@
                                             } finally {
                                                 /* Will be a no-op if nothing pending */
                                                 mAm.updateOomAdjPendingTargetsLocked(
-                                                        OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+                                                        OOM_ADJ_REASON_START_SERVICE);
                                             }
                                         } else {
                                             unbindServiceLocked(connection);
@@ -1235,8 +1248,7 @@
                             /* ignore - local call */
                         } finally {
                             /* Will be a no-op if nothing pending */
-                            mAm.updateOomAdjPendingTargetsLocked(
-                                    OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+                            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
                         }
                     } else { // Starting a service
                         try {
@@ -1310,7 +1322,7 @@
                 false /* packageFrozen */,
                 true /* enqueueOomAdj */);
         /* Will be a no-op if nothing pending */
-        mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+        mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
         if (error != null) {
             return new ComponentName("!!", error);
         }
@@ -1495,7 +1507,7 @@
                     stopServiceLocked(service, true);
                 }
                 if (size > 0) {
-                    mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                    mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UID_IDLE);
                 }
             }
         }
@@ -3240,6 +3252,12 @@
                 return;
             }
             Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr);
+            final long now = SystemClock.uptimeMillis();
+            logFGSStateChangeLocked(sr,
+                    FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT,
+                    now > sr.mFgsEnterTime ? (int) (now - sr.mFgsEnterTime) : 0,
+                    FGS_STOP_REASON_UNKNOWN,
+                    FGS_TYPE_POLICY_CHECK_UNKNOWN);
             try {
                 sr.app.getThread().scheduleTimeoutService(sr, sr.getShortFgsInfo().getStartId());
             } catch (RemoteException e) {
@@ -3289,7 +3307,7 @@
 
             Slog.e(TAG_SERVICE, "Short FGS procstate demoted: " + sr);
 
-            mAm.updateOomAdjLocked(sr.app, OomAdjuster.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT);
+            mAm.updateOomAdjLocked(sr.app, OOM_ADJ_REASON_SHORT_FGS_TIMEOUT);
         }
     }
 
@@ -3623,7 +3641,7 @@
                 needOomAdj = true;
                 if (bringUpServiceLocked(s, service.getFlags(), callerFg, false,
                         permissionsReviewRequired, packageFrozen, true) != null) {
-                    mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+                    mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE);
                     return 0;
                 }
             }
@@ -3648,7 +3666,7 @@
                 mAm.enqueueOomAdjTargetLocked(s.app);
             }
             if (needOomAdj) {
-                mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+                mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE);
             }
 
             final int packageState = wasStopped
@@ -3780,7 +3798,8 @@
                     }
                 }
 
-                serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false);
+                serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false,
+                        OOM_ADJ_REASON_EXECUTING_SERVICE);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -3871,7 +3890,7 @@
                 }
             }
 
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UNBIND_SERVICE);
 
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -3918,7 +3937,8 @@
                     }
                 }
 
-                serviceDoneExecutingLocked(r, inDestroying, false, false);
+                serviceDoneExecutingLocked(r, inDestroying, false, false,
+                        OOM_ADJ_REASON_UNBIND_SERVICE);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -4353,11 +4373,11 @@
     /**
      * Bump the given service record into executing state.
      * @param oomAdjReason The caller requests it to perform the oomAdjUpdate not {@link
-     *         OomAdjuster#OOM_ADJ_REASON_NONE}.
+     *         ActivityManagerInternal#OOM_ADJ_REASON_NONE}.
      * @return {@code true} if it performed oomAdjUpdate.
      */
     private boolean bumpServiceExecutingLocked(
-            ServiceRecord r, boolean fg, String why, @OomAdjuster.OomAdjReason int oomAdjReason) {
+            ServiceRecord r, boolean fg, String why, @OomAdjReason int oomAdjReason) {
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING "
                 + why + " of " + r + " in app " + r.app);
         else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING "
@@ -4409,7 +4429,7 @@
             }
         }
         boolean oomAdjusted = false;
-        if (oomAdjReason != OomAdjuster.OOM_ADJ_REASON_NONE && r.app != null
+        if (oomAdjReason != OOM_ADJ_REASON_NONE && r.app != null
                 && r.app.mState.getCurProcState() > ActivityManager.PROCESS_STATE_SERVICE) {
             // Force an immediate oomAdjUpdate, so the client app could be in the correct process
             // state before doing any service related transactions
@@ -4433,8 +4453,7 @@
                 + " rebind=" + rebind);
         if ((!i.requested || rebind) && i.apps.size() > 0) {
             try {
-                bumpServiceExecutingLocked(r, execInFg, "bind",
-                        OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+                bumpServiceExecutingLocked(r, execInFg, "bind", OOM_ADJ_REASON_BIND_SERVICE);
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                     Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestServiceBinding="
                             + i.intent.getIntent() + ". bindSeq=" + mBindServiceSeqCounter);
@@ -4450,13 +4469,15 @@
                 // Keep the executeNesting count accurate.
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e);
                 final boolean inDestroying = mDestroyingServices.contains(r);
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+                        OOM_ADJ_REASON_UNBIND_SERVICE);
                 throw e;
             } catch (RemoteException e) {
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r);
                 // Keep the executeNesting count accurate.
                 final boolean inDestroying = mDestroyingServices.contains(r);
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+                        OOM_ADJ_REASON_UNBIND_SERVICE);
                 return false;
             }
         }
@@ -4834,7 +4855,7 @@
             // Ignore, it's been logged and nothing upstack cares.
         } finally {
             /* Will be a no-op if nothing pending */
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
         }
     }
 
@@ -5186,13 +5207,14 @@
 
         final ProcessServiceRecord psr = app.mServices;
         final boolean newService = psr.startService(r);
-        bumpServiceExecutingLocked(r, execInFg, "create", OomAdjuster.OOM_ADJ_REASON_NONE);
+        bumpServiceExecutingLocked(r, execInFg, "create",
+                OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */);
         mAm.updateLruProcessLocked(app, false, null);
         updateServiceForegroundLocked(psr, /* oomAdj= */ false);
         // Force an immediate oomAdjUpdate, so the client app could be in the correct process state
         // before doing any service related transactions
         mAm.enqueueOomAdjTargetLocked(app);
-        mAm.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+        mAm.updateOomAdjLocked(app, OOM_ADJ_REASON_START_SERVICE);
 
         boolean created = false;
         try {
@@ -5226,7 +5248,8 @@
             if (!created) {
                 // Keep the executeNesting count accurate.
                 final boolean inDestroying = mDestroyingServices.contains(r);
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+                        OOM_ADJ_REASON_STOP_SERVICE);
 
                 // Cleanup.
                 if (newService) {
@@ -5312,7 +5335,8 @@
             mAm.grantImplicitAccess(r.userId, si.intent, si.callingId,
                     UserHandle.getAppId(r.appInfo.uid)
             );
-            bumpServiceExecutingLocked(r, execInFg, "start", OomAdjuster.OOM_ADJ_REASON_NONE);
+            bumpServiceExecutingLocked(r, execInFg, "start",
+                    OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */);
             if (r.fgRequired && !r.fgWaiting) {
                 if (!r.isForeground) {
                     if (DEBUG_BACKGROUND_CHECK) {
@@ -5338,7 +5362,7 @@
 
         if (!oomAdjusted) {
             mAm.enqueueOomAdjTargetLocked(r.app);
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
         }
         ParceledListSlice<ServiceStartArgs> slice = new ParceledListSlice<>(args);
         slice.setInlineCountLimit(4);
@@ -5364,10 +5388,11 @@
             // Keep nesting count correct
             final boolean inDestroying = mDestroyingServices.contains(r);
             for (int i = 0, size = args.size(); i < size; i++) {
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying, true);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, true,
+                        OOM_ADJ_REASON_STOP_SERVICE);
             }
             /* Will be a no-op if nothing pending */
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
             if (caughtException instanceof TransactionTooLargeException) {
                 throw (TransactionTooLargeException)caughtException;
             }
@@ -5454,7 +5479,7 @@
                 if (ibr.hasBound) {
                     try {
                         oomAdjusted |= bumpServiceExecutingLocked(r, false, "bring down unbind",
-                                OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                                OOM_ADJ_REASON_UNBIND_SERVICE);
                         ibr.hasBound = false;
                         ibr.requested = false;
                         r.app.getThread().scheduleUnbindService(r,
@@ -5608,7 +5633,7 @@
                 } else {
                     try {
                         oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
-                                oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                                oomAdjusted ? 0 : OOM_ADJ_REASON_STOP_SERVICE);
                         mDestroyingServices.add(r);
                         r.destroying = true;
                         r.app.getThread().scheduleStopService(r);
@@ -5630,7 +5655,7 @@
         if (!oomAdjusted) {
             mAm.enqueueOomAdjTargetLocked(r.app);
             if (!enqueueOomAdj) {
-                mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
             }
         }
         if (r.bindings.size() > 0) {
@@ -5755,8 +5780,7 @@
             if (s.app != null && s.app.getThread() != null && b.intent.apps.size() == 0
                     && b.intent.hasBound) {
                 try {
-                    bumpServiceExecutingLocked(s, false, "unbind",
-                            OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                    bumpServiceExecutingLocked(s, false, "unbind", OOM_ADJ_REASON_UNBIND_SERVICE);
                     if (b.client != s.app && c.notHasFlag(Context.BIND_WAIVE_PRIORITY)
                             && s.app.mState.getSetProcState() <= PROCESS_STATE_HEAVY_WEIGHT) {
                         // If this service's process is not already in the cached list,
@@ -5879,7 +5903,8 @@
                 }
             }
             final long origId = Binder.clearCallingIdentity();
-            serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj);
+            serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj,
+                    OOM_ADJ_REASON_EXECUTING_SERVICE);
             Binder.restoreCallingIdentity(origId);
         } else {
             Slog.w(TAG, "Done executing unknown service from pid "
@@ -5898,11 +5923,11 @@
                 r.tracker.setStarted(false, memFactor, now);
             }
         }
-        serviceDoneExecutingLocked(r, true, true, enqueueOomAdj);
+        serviceDoneExecutingLocked(r, true, true, enqueueOomAdj, OOM_ADJ_REASON_PROCESS_END);
     }
 
     private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
-            boolean finishing, boolean enqueueOomAdj) {
+            boolean finishing, boolean enqueueOomAdj, @OomAdjReason int oomAdjReason) {
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "<<< DONE EXECUTING " + r
                 + ": nesting=" + r.executeNesting
                 + ", inDestroying=" + inDestroying + ", app=" + r.app);
@@ -5938,7 +5963,7 @@
                 if (enqueueOomAdj) {
                     mAm.enqueueOomAdjTargetLocked(r.app);
                 } else {
-                    mAm.updateOomAdjLocked(r.app, OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                    mAm.updateOomAdjLocked(r.app, oomAdjReason);
                 }
             }
             r.executeFg = false;
@@ -6008,7 +6033,7 @@
                         bringDownServiceLocked(sr, true);
                     }
                     /* Will be a no-op if nothing pending */
-                    mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+                    mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
                 }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Exception in new application when starting service "
@@ -6068,7 +6093,7 @@
             }
         }
         if (needOomAdj) {
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_PROCESS_END);
         }
     }
 
@@ -6139,7 +6164,7 @@
                 bringDownServiceLocked(mTmpCollectionResults.get(i), true);
             }
             if (size > 0) {
-                mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_COMPONENT_DISABLED);
             }
             if (fullStop && !mTmpCollectionResults.isEmpty()) {
                 // if we're tearing down the app's entire service state, account for possible
@@ -6266,7 +6291,7 @@
             }
         }
         if (needOomAdj) {
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_REMOVE_TASK);
         }
     }
 
@@ -6437,7 +6462,7 @@
             }
         }
 
-        mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+        mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
 
         if (!allowRestart) {
             psr.stopAllServices();
@@ -7216,47 +7241,52 @@
      */
     protected boolean dumpService(FileDescriptor fd, PrintWriter pw, String name, int[] users,
             String[] args, int opti, boolean dumpAll) {
-        final ArrayList<ServiceRecord> services = new ArrayList<>();
+        try {
+            mAm.mOomAdjuster.mCachedAppOptimizer.enableFreezer(false);
+            final ArrayList<ServiceRecord> services = new ArrayList<>();
 
-        final Predicate<ServiceRecord> filter = DumpUtils.filterRecord(name);
+            final Predicate<ServiceRecord> filter = DumpUtils.filterRecord(name);
 
-        synchronized (mAm) {
-            if (users == null) {
-                users = mAm.mUserController.getUsers();
-            }
-
-            for (int user : users) {
-                ServiceMap smap = mServiceMap.get(user);
-                if (smap == null) {
-                    continue;
+            synchronized (mAm) {
+                if (users == null) {
+                    users = mAm.mUserController.getUsers();
                 }
-                ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByInstanceName;
-                for (int i=0; i<alls.size(); i++) {
-                    ServiceRecord r1 = alls.valueAt(i);
 
-                    if (filter.test(r1)) {
-                        services.add(r1);
+                for (int user : users) {
+                    ServiceMap smap = mServiceMap.get(user);
+                    if (smap == null) {
+                        continue;
+                    }
+                    ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByInstanceName;
+                    for (int i=0; i<alls.size(); i++) {
+                        ServiceRecord r1 = alls.valueAt(i);
+
+                        if (filter.test(r1)) {
+                            services.add(r1);
+                        }
                     }
                 }
             }
-        }
 
-        if (services.size() <= 0) {
-            return false;
-        }
-
-        // Sort by component name.
-        services.sort(Comparator.comparing(WithComponentName::getComponentName));
-
-        boolean needSep = false;
-        for (int i=0; i<services.size(); i++) {
-            if (needSep) {
-                pw.println();
+            if (services.size() <= 0) {
+                return false;
             }
-            needSep = true;
-            dumpService("", fd, pw, services.get(i), args, dumpAll);
+
+            // Sort by component name.
+            services.sort(Comparator.comparing(WithComponentName::getComponentName));
+
+            boolean needSep = false;
+            for (int i=0; i<services.size(); i++) {
+                if (needSep) {
+                    pw.println();
+                }
+                needSep = true;
+                dumpService("", fd, pw, services.get(i), args, dumpAll);
+            }
+            return true;
+        } finally {
+            mAm.mOomAdjuster.mCachedAppOptimizer.enableFreezer(true);
         }
-        return true;
     }
 
     /**
@@ -7898,7 +7928,8 @@
         boolean allowWhileInUsePermissionInFgs;
         @PowerExemptionManager.ReasonCode int fgsStartReasonCode;
         if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER
-                || state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) {
+                || state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT
+                || state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT) {
             allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgsAtEntering;
             fgsStartReasonCode = r.mAllowStartForegroundAtEntering;
         } else {
@@ -7932,9 +7963,9 @@
                 r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mClientUid : INVALID_UID,
                 r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mDelegationService
                         : ForegroundServiceDelegationOptions.DELEGATION_SERVICE_DEFAULT,
-                0,
-                null,
-                null);
+                0 /* api_sate */,
+                null /* api_type */,
+                null /* api_timestamp */);
 
         int event = 0;
         if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER) {
@@ -7943,7 +7974,9 @@
             event = EventLogTags.AM_FOREGROUND_SERVICE_STOP;
         } else if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED) {
             event = EventLogTags.AM_FOREGROUND_SERVICE_DENIED;
-        } else {
+        } else if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT) {
+            event = EventLogTags.AM_FOREGROUND_SERVICE_TIMED_OUT;
+        }else {
             // Unknown event.
             return;
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 06e6df9..0a19a70 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -44,6 +44,14 @@
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.OP_NONE;
 import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
@@ -199,6 +207,7 @@
 import android.app.ActivityManagerInternal.BroadcastEventListener;
 import android.app.ActivityManagerInternal.ForegroundServiceStateListener;
 import android.app.ActivityManagerInternal.MediaProjectionTokenEvent;
+import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityTaskManager.RootTaskInfo;
 import android.app.ActivityThread;
 import android.app.AnrController;
@@ -368,7 +377,6 @@
 import android.util.IndentingPrintWriter;
 import android.util.IntArray;
 import android.util.Log;
-import android.util.LogWriter;
 import android.util.Pair;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
@@ -1968,7 +1976,7 @@
                 app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM);
                 addPidLocked(app);
                 updateLruProcessLocked(app, false, null);
-                updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+                updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT);
             }
         } catch (PackageManager.NameNotFoundException e) {
             throw new RuntimeException(
@@ -2502,7 +2510,7 @@
         // bind background threads to little cores
         // this is expected to fail inside of framework tests because apps can't touch cpusets directly
         // make sure we've already adjusted system_server's internal view of itself first
-        updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT);
         try {
             Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(),
                     Process.THREAD_GROUP_SYSTEM);
@@ -3387,7 +3395,7 @@
             handleAppDiedLocked(app, pid, false, true, fromBinderDied);
 
             if (doOomAdj) {
-                updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+                updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
             }
             if (doLowMem) {
                 mAppProfiler.doLowMemReportIfNeededLocked(app);
@@ -4845,7 +4853,7 @@
             }
 
             if (!didSomething) {
-                updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+                updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
                 checkTime(startTime, "finishAttachApplicationInner: after updateOomAdjLocked");
             }
 
@@ -5487,7 +5495,7 @@
                 "setProcessLimit()");
         synchronized (this) {
             mConstants.setOverrideMaxCachedProcesses(max);
-            trimApplicationsLocked(true, OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+            trimApplicationsLocked(true, OOM_ADJ_REASON_PROCESS_END);
         }
     }
 
@@ -5515,7 +5523,7 @@
                 pr.mState.setForcingToImportant(null);
                 clearProcessForegroundLocked(pr);
             }
-            updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+            updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
         }
     }
 
@@ -5562,7 +5570,7 @@
             }
 
             if (changed) {
-                updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+                updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
             }
         }
     }
@@ -6871,7 +6879,7 @@
                     new HostingRecord(HostingRecord.HOSTING_TYPE_ADDED_APPLICATION,
                             customProcess != null ? customProcess : info.processName));
             updateLruProcessLocked(app, false, null);
-            updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+            updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
         }
 
         // Report usage as process is persistent and being started.
@@ -6988,7 +6996,7 @@
                 mOomAdjProfiler.onWakefulnessChanged(wakefulness);
                 mOomAdjuster.onWakefulnessChanged(wakefulness);
 
-                updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+                updateOomAdjLocked(OOM_ADJ_REASON_UI_VISIBILITY);
             }
         }
     }
@@ -7750,7 +7758,7 @@
                     }
                 }
                 if (changed) {
-                    updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+                    updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
                 }
             }
         } finally {
@@ -9510,7 +9518,7 @@
 
             mAppProfiler.setMemFactorOverrideLocked(level);
             // Kick off an oom adj update since we forced a mem factor update.
-            updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+            updateOomAdjLocked(OOM_ADJ_REASON_SHELL);
         }
     }
 
@@ -13410,7 +13418,7 @@
             proc.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP);
 
             // Try not to kill the process during backup
-            updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
+            updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP);
 
             // If the process is already attached, schedule the creation of the backup agent now.
             // If it is not yet live, this will be done when it attaches to the framework.
@@ -13534,7 +13542,7 @@
 
                 // Not backing this app up any more; reset its OOM adjustment
                 final ProcessRecord proc = backupTarget.app;
-                updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
+                updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP);
                 proc.setInFullBackup(false);
                 proc.mProfile.clearHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP);
 
@@ -13715,7 +13723,7 @@
                 if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver(
                         /*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) {
                     throw new SecurityException("SDK sandbox not allowed to register receiver"
-                            + " with the given IntentFilter: " + filter.toString());
+                            + " with the given IntentFilter: " + filter.toLongString());
                 }
             }
 
@@ -13925,7 +13933,7 @@
                 // If we actually concluded any broadcasts, we might now be able
                 // to trim the recipients' apps from our working set
                 if (doTrim) {
-                    trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER);
+                    trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
                     return;
                 }
             }
@@ -15187,7 +15195,7 @@
                 queue.finishReceiverLocked(callerApp, resultCode,
                         resultData, resultExtras, resultAbort, true);
                 // updateOomAdjLocked() will be done here
-                trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER);
+                trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
             }
 
         } finally {
@@ -16132,7 +16140,7 @@
             item.foregroundServiceTypes = fgServiceTypes;
         }
         if (oomAdj) {
-            updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+            updateOomAdjLocked(proc, OOM_ADJ_REASON_UI_VISIBILITY);
         }
     }
 
@@ -16198,7 +16206,7 @@
      * {@link #enqueueOomAdjTargetLocked}.
      */
     @GuardedBy("this")
-    void updateOomAdjPendingTargetsLocked(@OomAdjuster.OomAdjReason int oomAdjReason) {
+    void updateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
         mOomAdjuster.updateOomAdjPendingTargetsLocked(oomAdjReason);
     }
 
@@ -16217,7 +16225,7 @@
     }
 
     @GuardedBy("this")
-    final void updateOomAdjLocked(@OomAdjuster.OomAdjReason int oomAdjReason) {
+    final void updateOomAdjLocked(@OomAdjReason int oomAdjReason) {
         mOomAdjuster.updateOomAdjLocked(oomAdjReason);
     }
 
@@ -16229,8 +16237,7 @@
      * @return whether updateOomAdjLocked(app) was successful.
      */
     @GuardedBy("this")
-    final boolean updateOomAdjLocked(
-            ProcessRecord app, @OomAdjuster.OomAdjReason int oomAdjReason) {
+    final boolean updateOomAdjLocked(ProcessRecord app, @OomAdjReason int oomAdjReason) {
         return mOomAdjuster.updateOomAdjLocked(app, oomAdjReason);
     }
 
@@ -16463,16 +16470,14 @@
         mOomAdjuster.setUidTempAllowlistStateLSP(uid, onAllowlist);
     }
 
-    private void trimApplications(
-            boolean forceFullOomAdj, @OomAdjuster.OomAdjReason int oomAdjReason) {
+    private void trimApplications(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
         synchronized (this) {
             trimApplicationsLocked(forceFullOomAdj, oomAdjReason);
         }
     }
 
     @GuardedBy("this")
-    private void trimApplicationsLocked(
-            boolean forceFullOomAdj, @OomAdjuster.OomAdjReason int oomAdjReason) {
+    private void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
         // First remove any unused application processes whose package
         // has been removed.
         boolean didSomething = false;
@@ -17444,7 +17449,7 @@
                 }
                 pr.mState.setHasOverlayUi(hasOverlayUi);
                 //Slog.i(TAG, "Setting hasOverlayUi=" + pr.hasOverlayUi + " for pid=" + pid);
-                updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+                updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
             }
         }
 
@@ -17579,7 +17584,7 @@
 
         @Override
         public void trimApplications() {
-            ActivityManagerService.this.trimApplications(true, OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+            ActivityManagerService.this.trimApplications(true, OOM_ADJ_REASON_ACTIVITY);
         }
 
         public void killProcessesForRemovedTask(ArrayList<Object> procsToKill) {
@@ -17628,9 +17633,9 @@
         }
 
         @Override
-        public void updateOomAdj() {
+        public void updateOomAdj(@OomAdjReason int oomAdjReason) {
             synchronized (ActivityManagerService.this) {
-                ActivityManagerService.this.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+                ActivityManagerService.this.updateOomAdjLocked(oomAdjReason);
             }
         }
 
@@ -18290,8 +18295,7 @@
             // sends to the activity. After this race issue between WM/ATMS and AMS is solved, this
             // workaround can be removed. (b/213288355)
             if (isNewPending) {
-                mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid,
-                        OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+                mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid, OOM_ADJ_REASON_ACTIVITY);
             }
             // We need to update the network rules for the app coming to the top state so that
             // it can access network when the device or the app is in a restricted state
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 350ac3b..e080a80 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -59,12 +59,12 @@
 import android.app.IActivityTaskManager;
 import android.app.IProcessObserver;
 import android.app.IStopUserCallback;
-import android.app.IUidObserver;
 import android.app.IUserSwitchObserver;
 import android.app.KeyguardManager;
 import android.app.ProcessStateEnum;
 import android.app.ProfilerInfo;
 import android.app.RemoteServiceException.CrashedByAdbException;
+import android.app.UidObserver;
 import android.app.UserSwitchObserver;
 import android.app.WaitResult;
 import android.app.usage.AppStandbyInfo;
@@ -1858,7 +1858,7 @@
         return 0;
     }
 
-    static final class MyUidObserver extends IUidObserver.Stub
+    static final class MyUidObserver extends UidObserver
             implements ActivityManagerService.OomAdjObserver {
         final IActivityManager mInterface;
         final ActivityManagerService mInternal;
@@ -1883,8 +1883,7 @@
         }
 
         @Override
-        public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability)
-                throws RemoteException {
+        public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
             synchronized (this) {
                 final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
                 try {
@@ -1903,7 +1902,7 @@
         }
 
         @Override
-        public void onUidGone(int uid, boolean disabled) throws RemoteException {
+        public void onUidGone(int uid, boolean disabled) {
             synchronized (this) {
                 final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
                 try {
@@ -1921,7 +1920,7 @@
         }
 
         @Override
-        public void onUidActive(int uid) throws RemoteException {
+        public void onUidActive(int uid) {
             synchronized (this) {
                 final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
                 try {
@@ -1935,7 +1934,7 @@
         }
 
         @Override
-        public void onUidIdle(int uid, boolean disabled) throws RemoteException {
+        public void onUidIdle(int uid, boolean disabled) {
             synchronized (this) {
                 final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
                 try {
@@ -1953,7 +1952,7 @@
         }
 
         @Override
-        public void onUidCachedChanged(int uid, boolean cached) throws RemoteException {
+        public void onUidCachedChanged(int uid, boolean cached) {
             synchronized (this) {
                 final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
                 try {
@@ -1967,10 +1966,6 @@
         }
 
         @Override
-        public void onUidProcAdjChanged(int uid) throws RemoteException {
-        }
-
-        @Override
         public void onOomAdjMessage(String msg) {
             synchronized (this) {
                 final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index dbb351b..9e61ce4 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -18,7 +18,6 @@
 
 import static com.android.internal.util.Preconditions.checkState;
 import static com.android.server.am.BroadcastRecord.deliveryStateToString;
-import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
 import static com.android.server.am.BroadcastRecord.isReceiverEquals;
 
 import android.annotation.IntDef;
@@ -441,17 +440,17 @@
     }
 
     public int getPreferredSchedulingGroupLocked() {
-        if (mCountForeground > mCountForegroundDeferred) {
+        if (!isActive()) {
+            return ProcessList.SCHED_GROUP_UNDEFINED;
+        } else if (mCountForeground > mCountForegroundDeferred) {
             // We have a foreground broadcast somewhere down the queue, so
             // boost priority until we drain them all
             return ProcessList.SCHED_GROUP_DEFAULT;
         } else if ((mActive != null) && mActive.isForeground()) {
             // We have a foreground broadcast right now, so boost priority
             return ProcessList.SCHED_GROUP_DEFAULT;
-        } else if (!isIdle()) {
-            return ProcessList.SCHED_GROUP_BACKGROUND;
         } else {
-            return ProcessList.SCHED_GROUP_UNDEFINED;
+            return ProcessList.SCHED_GROUP_BACKGROUND;
         }
     }
 
@@ -709,7 +708,7 @@
                 || consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit);
         final boolean isLPQueueEligible = shouldConsiderLPQueue
                 && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime
-                && !blockedOnOrderedDispatch(nextLPRecord, nextLPRecordIndex);
+                && !nextLPRecord.isBlocked(nextLPRecordIndex);
         return isLPQueueEligible ? lowPriorityQueue : highPriorityQueue;
     }
 
@@ -912,39 +911,20 @@
         }
     }
 
-    private boolean blockedOnOrderedDispatch(BroadcastRecord r, int index) {
-        final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
-
-        int existingDeferredCount = 0;
-        if (r.deferUntilActive) {
-            for (int i = 0; i < index; i++) {
-                if (r.deferredUntilActive[i]) existingDeferredCount++;
-            }
-        }
-
-        // We might be blocked waiting for other receivers to finish,
-        // typically for an ordered broadcast or priority traunches
-        if ((r.terminalCount + existingDeferredCount) < blockedUntilTerminalCount
-                && !isDeliveryStateTerminal(r.getDeliveryState(index))) {
-            return true;
-        }
-        return false;
-    }
-
     /**
      * Update {@link #getRunnableAt()} if it's currently invalidated.
      */
     private void updateRunnableAt() {
-        final SomeArgs next = peekNextBroadcast();
+        if (!mRunnableAtInvalidated) return;
         mRunnableAtInvalidated = false;
+
+        final SomeArgs next = peekNextBroadcast();
         if (next != null) {
             final BroadcastRecord r = (BroadcastRecord) next.arg1;
             final int index = next.argi1;
             final long runnableAt = r.enqueueTime;
 
-            // If we're specifically queued behind other ordered dispatch activity,
-            // we aren't runnable yet
-            if (blockedOnOrderedDispatch(r, index)) {
+            if (r.isBlocked(index)) {
                 mRunnableAt = Long.MAX_VALUE;
                 mRunnableAtReason = REASON_BLOCKED;
                 return;
@@ -1262,12 +1242,12 @@
             pw.print(info.activityInfo.name);
         }
         pw.println();
-        final int blockedUntilTerminalCount = record.blockedUntilTerminalCount[recordIndex];
-        if (blockedUntilTerminalCount != -1) {
+        final int blockedUntilBeyondCount = record.blockedUntilBeyondCount[recordIndex];
+        if (blockedUntilBeyondCount != -1) {
             pw.print("    blocked until ");
-            pw.print(blockedUntilTerminalCount);
+            pw.print(blockedUntilBeyondCount);
             pw.print(", currently at ");
-            pw.print(record.terminalCount);
+            pw.print(record.beyondCount);
             pw.print(" of ");
             pw.println(record.receivers.size());
         }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index bd36c3f..5a4d315 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -17,6 +17,7 @@
 package com.android.server.am;
 
 import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
 import static android.text.TextUtils.formatSimple;
@@ -37,7 +38,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 5752970..9a5cad1 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
 
@@ -38,7 +39,6 @@
 import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
 import static com.android.server.am.BroadcastRecord.getReceiverUid;
 import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1008,6 +1008,7 @@
         }
 
         final BroadcastRecord r = queue.getActive();
+        final int index = queue.getActiveIndex();
         if (r.ordered) {
             r.resultCode = resultCode;
             r.resultData = resultData;
@@ -1015,18 +1016,24 @@
             if (!r.isNoAbort()) {
                 r.resultAbort = resultAbort;
             }
+        }
 
-            // When the caller aborted an ordered broadcast, we mark all
-            // remaining receivers as skipped
-            if (r.resultAbort) {
-                for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) {
-                    setDeliveryState(null, null, r, i, r.receivers.get(i),
-                            BroadcastRecord.DELIVERY_SKIPPED, "resultAbort");
-                }
+        // To ensure that "beyond" high-water marks are updated in a monotonic
+        // way, we finish this receiver before possibly skipping any remaining
+        // aborted receivers
+        final boolean res = finishReceiverActiveLocked(queue,
+                BroadcastRecord.DELIVERY_DELIVERED, "remote app");
+
+        // When the caller aborted an ordered broadcast, we mark all
+        // remaining receivers as skipped
+        if (r.resultAbort) {
+            for (int i = index + 1; i < r.receivers.size(); i++) {
+                setDeliveryState(null, null, r, i, r.receivers.get(i),
+                        BroadcastRecord.DELIVERY_SKIPPED, "resultAbort");
             }
         }
 
-        return finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app");
+        return res;
     }
 
     /**
@@ -1108,21 +1115,10 @@
             @NonNull Object receiver, @DeliveryState int newDeliveryState,
             @NonNull String reason) {
         final int cookie = traceBegin("setDeliveryState");
-        final int oldDeliveryState = getDeliveryState(r, index);
-        boolean checkFinished = false;
 
-        // Only apply state when we haven't already reached a terminal state;
-        // this is how we ignore racing timeout messages
-        if (!isDeliveryStateTerminal(oldDeliveryState)) {
-            r.setDeliveryState(index, newDeliveryState, reason);
-            if (oldDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) {
-                r.deferredCount--;
-            } else if (newDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) {
-                // If we're deferring a broadcast, maybe that's enough to unblock the final callback
-                r.deferredCount++;
-                checkFinished = true;
-            }
-        }
+        // Remember the old state and apply the new state
+        final int oldDeliveryState = getDeliveryState(r, index);
+        final boolean beyondCountChanged = r.setDeliveryState(index, newDeliveryState, reason);
 
         // Emit any relevant tracing results when we're changing the delivery
         // state as part of running from a queue
@@ -1147,15 +1143,13 @@
                         + deliveryStateToString(newDeliveryState) + " because " + reason);
             }
 
-            r.terminalCount++;
             notifyFinishReceiver(queue, app, r, index, receiver);
-            checkFinished = true;
         }
-        // When entire ordered broadcast finished, deliver final result
-        if (checkFinished) {
-            final boolean recordFinished =
-                    ((r.terminalCount + r.deferredCount) == r.receivers.size());
-            if (recordFinished) {
+
+        // When we've reached a new high-water mark, we might be in a position
+        // to unblock other receivers or the final resultTo
+        if (beyondCountChanged) {
+            if (r.beyondCount == r.receivers.size()) {
                 scheduleResultTo(r);
             }
 
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index c368290..64fe393 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -24,6 +24,7 @@
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY;
 
+import android.annotation.CheckResult;
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.IntDef;
@@ -101,8 +102,7 @@
     final @NonNull List<Object> receivers;   // contains BroadcastFilter and ResolveInfo
     final @DeliveryState int[] delivery;   // delivery state of each receiver
     final @NonNull String[] deliveryReasons; // reasons for delivery state of each receiver
-    final boolean[] deferredUntilActive; // whether each receiver is infinitely deferred
-    final int[] blockedUntilTerminalCount; // blocked until count of each receiver
+    final int[] blockedUntilBeyondCount; // blocked until count of each receiver
     @Nullable ProcessRecord resultToApp; // who receives final result if non-null
     @Nullable IIntentReceiver resultTo; // who receives final result if non-null
     boolean deferred;
@@ -134,6 +134,7 @@
     int manifestSkipCount;  // number of manifest receivers skipped.
     int terminalCount;      // number of receivers in terminal state.
     int deferredCount;      // number of receivers in deferred state.
+    int beyondCount;        // high-water number of receivers we've moved beyond.
     @Nullable BroadcastQueue queue;   // the outbound queue handling this broadcast
 
     // Determines the privileges the app's process has in regard to background starts.
@@ -219,6 +220,23 @@
         }
     }
 
+    /**
+     * Return if the given delivery state is "beyond", which means that we've
+     * moved beyond this receiver, and future receivers are now unblocked.
+     */
+    static boolean isDeliveryStateBeyond(@DeliveryState int deliveryState) {
+        switch (deliveryState) {
+            case DELIVERY_DELIVERED:
+            case DELIVERY_SKIPPED:
+            case DELIVERY_TIMEOUT:
+            case DELIVERY_FAILURE:
+            case DELIVERY_DEFERRED:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     ProcessRecord curApp;       // hosting application of current receiver.
     ComponentName curComponent; // the receiver class that is currently running.
     ActivityInfo curReceiver;   // the manifest receiver that is currently running.
@@ -356,7 +374,7 @@
                 TimeUtils.formatDuration(terminalTime[i] - scheduledTime[i], pw);
                 pw.print(' ');
             }
-            pw.print("("); pw.print(blockedUntilTerminalCount[i]); pw.print(") ");
+            pw.print("("); pw.print(blockedUntilBeyondCount[i]); pw.print(") ");
             pw.print("#"); pw.print(i); pw.print(": ");
             if (o instanceof BroadcastFilter) {
                 pw.println(o);
@@ -411,8 +429,7 @@
         urgent = calculateUrgent(_intent, _options);
         deferUntilActive = calculateDeferUntilActive(_callingUid,
                 _options, _resultTo, _serialized, urgent);
-        deferredUntilActive = new boolean[deferUntilActive ? delivery.length : 0];
-        blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
+        blockedUntilBeyondCount = calculateBlockedUntilBeyondCount(receivers, _serialized);
         scheduledTime = new long[delivery.length];
         terminalTime = new long[delivery.length];
         resultToApp = _resultToApp;
@@ -423,7 +440,7 @@
         ordered = _serialized;
         sticky = _sticky;
         initialSticky = _initialSticky;
-        prioritized = isPrioritized(blockedUntilTerminalCount, _serialized);
+        prioritized = isPrioritized(blockedUntilBeyondCount, _serialized);
         userId = _userId;
         nextReceiver = 0;
         state = IDLE;
@@ -467,8 +484,7 @@
         delivery = from.delivery;
         deliveryReasons = from.deliveryReasons;
         deferUntilActive = from.deferUntilActive;
-        deferredUntilActive = from.deferredUntilActive;
-        blockedUntilTerminalCount = from.blockedUntilTerminalCount;
+        blockedUntilBeyondCount = from.blockedUntilBeyondCount;
         scheduledTime = from.scheduledTime;
         terminalTime = from.terminalTime;
         resultToApp = from.resultToApp;
@@ -627,32 +643,72 @@
     /**
      * Update the delivery state of the given {@link #receivers} index.
      * Automatically updates any time measurements related to state changes.
+     *
+     * @return if {@link #beyondCount} changed due to this state transition,
+     *         indicating that other events may be unblocked.
      */
-    void setDeliveryState(int index, @DeliveryState int deliveryState,
+    @CheckResult
+    boolean setDeliveryState(int index, @DeliveryState int newDeliveryState,
             @NonNull String reason) {
-        delivery[index] = deliveryState;
-        deliveryReasons[index] = reason;
-        if (deferUntilActive) deferredUntilActive[index] = false;
-        switch (deliveryState) {
+        final int oldDeliveryState = delivery[index];
+        if (isDeliveryStateTerminal(oldDeliveryState)
+                || newDeliveryState == oldDeliveryState) {
+            // We've already arrived in terminal or requested state, so leave
+            // any statistics and reasons intact from the first transition
+            return false;
+        }
+
+        switch (oldDeliveryState) {
+            case DELIVERY_DEFERRED:
+                deferredCount--;
+                break;
+        }
+        switch (newDeliveryState) {
+            case DELIVERY_SCHEDULED:
+                scheduledTime[index] = SystemClock.uptimeMillis();
+                break;
+            case DELIVERY_DEFERRED:
+                deferredCount++;
+                break;
             case DELIVERY_DELIVERED:
             case DELIVERY_SKIPPED:
             case DELIVERY_TIMEOUT:
             case DELIVERY_FAILURE:
                 terminalTime[index] = SystemClock.uptimeMillis();
-                break;
-            case DELIVERY_SCHEDULED:
-                scheduledTime[index] = SystemClock.uptimeMillis();
-                break;
-            case DELIVERY_DEFERRED:
-                if (deferUntilActive) deferredUntilActive[index] = true;
+                terminalCount++;
                 break;
         }
+
+        delivery[index] = newDeliveryState;
+        deliveryReasons[index] = reason;
+
+        // If this state change might bring us to a new high-water mark, bring
+        // ourselves as high as we possibly can
+        final int oldBeyondCount = beyondCount;
+        if (index >= beyondCount) {
+            for (int i = beyondCount; i < delivery.length; i++) {
+                if (isDeliveryStateBeyond(getDeliveryState(i))) {
+                    beyondCount = i + 1;
+                } else {
+                    break;
+                }
+            }
+        }
+        return (beyondCount != oldBeyondCount);
     }
 
     @DeliveryState int getDeliveryState(int index) {
         return delivery[index];
     }
 
+    /**
+     * @return if the given {@link #receivers} index should be considered
+     *         blocked based on the current status of the overall broadcast.
+     */
+    boolean isBlocked(int index) {
+        return (beyondCount < blockedUntilBeyondCount[index]);
+    }
+
     boolean wasDeliveryAttempted(int index) {
         final int deliveryState = getDeliveryState(index);
         switch (deliveryState) {
@@ -757,36 +813,36 @@
      * has prioritized tranches of receivers.
      */
     @VisibleForTesting
-    static boolean isPrioritized(@NonNull int[] blockedUntilTerminalCount,
+    static boolean isPrioritized(@NonNull int[] blockedUntilBeyondCount,
             boolean ordered) {
-        return !ordered && (blockedUntilTerminalCount.length > 0)
-                && (blockedUntilTerminalCount[0] != -1);
+        return !ordered && (blockedUntilBeyondCount.length > 0)
+                && (blockedUntilBeyondCount[0] != -1);
     }
 
     /**
-     * Calculate the {@link #terminalCount} that each receiver should be
+     * Calculate the {@link #beyondCount} that each receiver should be
      * considered blocked until.
      * <p>
      * For example, in an ordered broadcast, receiver {@code N} is blocked until
-     * receiver {@code N-1} reaches a terminal state. Similarly, in a
-     * prioritized broadcast, receiver {@code N} is blocked until all receivers
-     * of a higher priority reach a terminal state.
+     * receiver {@code N-1} reaches a terminal or deferred state. Similarly, in
+     * a prioritized broadcast, receiver {@code N} is blocked until all
+     * receivers of a higher priority reach a terminal or deferred state.
      * <p>
-     * When there are no terminal count constraints, the blocked value for each
+     * When there are no beyond count constraints, the blocked value for each
      * receiver is {@code -1}.
      */
     @VisibleForTesting
-    static @NonNull int[] calculateBlockedUntilTerminalCount(
+    static @NonNull int[] calculateBlockedUntilBeyondCount(
             @NonNull List<Object> receivers, boolean ordered) {
         final int N = receivers.size();
-        final int[] blockedUntilTerminalCount = new int[N];
+        final int[] blockedUntilBeyondCount = new int[N];
         int lastPriority = 0;
         int lastPriorityIndex = 0;
         for (int i = 0; i < N; i++) {
             if (ordered) {
                 // When sending an ordered broadcast, we need to block this
                 // receiver until all previous receivers have terminated
-                blockedUntilTerminalCount[i] = i;
+                blockedUntilBeyondCount[i] = i;
             } else {
                 // When sending a prioritized broadcast, we only need to wait
                 // for the previous tranche of receivers to be terminated
@@ -794,18 +850,18 @@
                 if ((i == 0) || (thisPriority != lastPriority)) {
                     lastPriority = thisPriority;
                     lastPriorityIndex = i;
-                    blockedUntilTerminalCount[i] = i;
+                    blockedUntilBeyondCount[i] = i;
                 } else {
-                    blockedUntilTerminalCount[i] = lastPriorityIndex;
+                    blockedUntilBeyondCount[i] = lastPriorityIndex;
                 }
             }
         }
         // If the entire list is in the same priority tranche, mark as -1 to
         // indicate that none of them need to wait
-        if (N > 0 && blockedUntilTerminalCount[N - 1] == 0) {
-            Arrays.fill(blockedUntilTerminalCount, -1);
+        if (N > 0 && blockedUntilBeyondCount[N - 1] == 0) {
+            Arrays.fill(blockedUntilBeyondCount, -1);
         }
-        return blockedUntilTerminalCount;
+        return blockedUntilBeyondCount;
     }
 
     static int getReceiverUid(@NonNull Object receiver) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 78edbba..f42087f 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -18,6 +18,28 @@
 
 import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
 import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
 import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION;
@@ -26,6 +48,7 @@
 
 import android.annotation.IntDef;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityThread;
 import android.app.ApplicationExitInfo;
 import android.app.IApplicationThread;
@@ -139,6 +162,26 @@
             FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BINDER_TXNS;
     static final int UNFREEZE_REASON_FEATURE_FLAGS =
             FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FEATURE_FLAGS;
+    static final int UNFREEZE_REASON_SHORT_FGS_TIMEOUT =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHORT_FGS_TIMEOUT;
+    static final int UNFREEZE_REASON_SYSTEM_INIT =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SYSTEM_INIT;
+    static final int UNFREEZE_REASON_BACKUP =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BACKUP;
+    static final int UNFREEZE_REASON_SHELL =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHELL;
+    static final int UNFREEZE_REASON_REMOVE_TASK =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_REMOVE_TASK;
+    static final int UNFREEZE_REASON_UID_IDLE =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_UID_IDLE;
+    static final int UNFREEZE_REASON_STOP_SERVICE =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_STOP_SERVICE;
+    static final int UNFREEZE_REASON_EXECUTING_SERVICE =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_EXECUTING_SERVICE;
+    static final int UNFREEZE_REASON_RESTRICTION_CHANGE =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_RESTRICTION_CHANGE;
+    static final int UNFREEZE_REASON_COMPONENT_DISABLED =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_COMPONENT_DISABLED;
 
     @IntDef(prefix = {"UNFREEZE_REASON_"}, value = {
         UNFREEZE_REASON_NONE,
@@ -160,6 +203,16 @@
         UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE,
         UNFREEZE_REASON_BINDER_TXNS,
         UNFREEZE_REASON_FEATURE_FLAGS,
+        UNFREEZE_REASON_SHORT_FGS_TIMEOUT,
+        UNFREEZE_REASON_SYSTEM_INIT,
+        UNFREEZE_REASON_BACKUP,
+        UNFREEZE_REASON_SHELL,
+        UNFREEZE_REASON_REMOVE_TASK,
+        UNFREEZE_REASON_UID_IDLE,
+        UNFREEZE_REASON_STOP_SERVICE,
+        UNFREEZE_REASON_EXECUTING_SERVICE,
+        UNFREEZE_REASON_RESTRICTION_CHANGE,
+        UNFREEZE_REASON_COMPONENT_DISABLED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UnfreezeReason {}
@@ -178,6 +231,7 @@
     private static final String ATRACE_FREEZER_TRACK = "Freezer";
 
     private static final int FREEZE_BINDER_TIMEOUT_MS = 100;
+    private static final int FREEZE_DEADLOCK_TIMEOUT_MS = 1000;
 
     @VisibleForTesting static final boolean ENABLE_FILE_COMPACT = false;
 
@@ -244,6 +298,7 @@
     static final int REPORT_UNFREEZE_MSG = 4;
     static final int COMPACT_NATIVE_MSG = 5;
     static final int UID_FROZEN_STATE_CHANGED_MSG = 6;
+    static final int DEADLOCK_WATCHDOG_MSG = 7;
 
     // When free swap falls below this percentage threshold any full (file + anon)
     // compactions will be downgraded to file only compactions to reduce pressure
@@ -1327,7 +1382,7 @@
         }
 
         try {
-            traceAppFreeze(app.processName, pid, false);
+            traceAppFreeze(app.processName, pid, reason);
             Process.setProcessFrozen(pid, app.uid, false);
 
             opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
@@ -1339,7 +1394,7 @@
         }
 
         if (!opt.isFrozen()) {
-            Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName);
+            Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName + " for " + reason);
 
             mFreezeHandler.sendMessage(
                     mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
@@ -1363,13 +1418,13 @@
      * The caller of this function should still trigger updateOomAdj for AMS to unfreeze the app.
      * @param pid pid of the process to be unfrozen
      */
-    void unfreezeProcess(int pid, @OomAdjuster.OomAdjReason int reason) {
+    void unfreezeProcess(int pid, @OomAdjReason int reason) {
         synchronized (mFreezerLock) {
             ProcessRecord app = mFrozenProcesses.get(pid);
             if (app == null) {
                 return;
             }
-            Slog.d(TAG_AM, "quick sync unfreeze " + pid);
+            Slog.d(TAG_AM, "quick sync unfreeze " + pid + " for " +  reason);
             try {
                 freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS);
             } catch (RuntimeException e) {
@@ -1378,7 +1433,7 @@
             }
 
             try {
-                traceAppFreeze(app.processName, pid, false);
+                traceAppFreeze(app.processName, pid, reason);
                 Process.setProcessFrozen(pid, app.uid, false);
             } catch (Exception e) {
                 Slog.e(TAG_AM, "Unable to quick unfreeze " + pid);
@@ -1386,9 +1441,15 @@
         }
     }
 
-    private static void traceAppFreeze(String processName, int pid, boolean freeze) {
+    /**
+     * Trace app freeze status
+     * @param processName The name of the target process
+     * @param pid The pid of the target process
+     * @param reason UNFREEZE_REASON_XXX (>=0) for unfreezing and -1 for freezing
+     */
+    private static void traceAppFreeze(String processName, int pid, int reason) {
         Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_FREEZER_TRACK,
-                (freeze ? "Freeze " : "Unfreeze ") + processName + ":" + pid);
+                (reason < 0 ? "Freeze " : "Unfreeze ") + processName + ":" + pid + " " + reason);
     }
 
     /**
@@ -1538,12 +1599,12 @@
         public long mOrigAnonRss;
         public int mProcState;
         public int mOomAdj;
-        public @OomAdjuster.OomAdjReason int mOomAdjReason;
+        public @OomAdjReason int mOomAdjReason;
 
         SingleCompactionStats(long[] rss, CompactSource source, String processName,
                 long deltaAnonRss, long zramConsumed, long anonMemFreed, long origAnonRss,
                 long cpuTimeMillis, int procState, int oomAdj,
-                @OomAdjuster.OomAdjReason int oomAdjReason, int uid) {
+                @OomAdjReason int oomAdjReason, int uid) {
             mRssAfterCompaction = rss;
             mSourceType = source;
             mProcessName = processName;
@@ -1947,29 +2008,15 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case SET_FROZEN_PROCESS_MSG:
-                {
                     ProcessRecord proc = (ProcessRecord) msg.obj;
-                    int pid = proc.getPid();
-                    final String name = proc.processName;
                     synchronized (mAm) {
                         freezeProcess(proc);
                     }
-                    try {
-                        // post-check to prevent deadlock
-                        mProcLocksReader.handleBlockingFileLocks(this);
-                    } catch (Exception e) {
-                        Slog.e(TAG_AM, "Unable to check file locks for "
-                                + name + "(" + pid + "): " + e);
-                        synchronized (mAm) {
-                            synchronized (mProcLock) {
-                                unfreezeAppLSP(proc, UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE);
-                            }
-                        }
-                    }
                     if (proc.mOptRecord.isFrozen()) {
                         onProcessFrozen(proc);
+                        removeMessages(DEADLOCK_WATCHDOG_MSG);
+                        sendEmptyMessageDelayed(DEADLOCK_WATCHDOG_MSG, FREEZE_DEADLOCK_TIMEOUT_MS);
                     }
-                }
                     break;
                 case REPORT_UNFREEZE_MSG:
                     int pid = msg.arg1;
@@ -1981,8 +2028,18 @@
                     reportUnfreeze(pid, frozenDuration, processName, reason);
                     break;
                 case UID_FROZEN_STATE_CHANGED_MSG:
-                    ProcessRecord proc = (ProcessRecord) msg.obj;
-                    reportOneUidFrozenStateChanged(proc.uid, true);
+                    reportOneUidFrozenStateChanged(((ProcessRecord) msg.obj).uid, true);
+                    break;
+                case DEADLOCK_WATCHDOG_MSG:
+                    try {
+                        // post-check to prevent deadlock
+                        if (DEBUG_FREEZER) {
+                            Slog.d(TAG_AM, "Freezer deadlock watchdog");
+                        }
+                        mProcLocksReader.handleBlockingFileLocks(this);
+                    } catch (IOException e) {
+                        Slog.w(TAG_AM, "Unable to check file locks");
+                    }
                     break;
                 default:
                     return;
@@ -2065,7 +2122,7 @@
                 long unfreezeTime = opt.getFreezeUnfreezeTime();
 
                 try {
-                    traceAppFreeze(proc.processName, pid, true);
+                    traceAppFreeze(proc.processName, pid, -1);
                     Process.setProcessFrozen(pid, proc.uid, true);
 
                     opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
@@ -2129,7 +2186,7 @@
         private void reportUnfreeze(int pid, int frozenDuration, String processName,
                 @UnfreezeReason int reason) {
 
-            EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName);
+            EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName, reason);
 
             // See above for why we're not taking mPhenotypeFlagLock here
             if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
@@ -2203,32 +2260,52 @@
         }
     }
 
-    static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjuster.OomAdjReason int oomAdjReason) {
+    static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjReason int oomAdjReason) {
         switch (oomAdjReason) {
-            case OomAdjuster.OOM_ADJ_REASON_ACTIVITY:
+            case OOM_ADJ_REASON_ACTIVITY:
                 return UNFREEZE_REASON_ACTIVITY;
-            case OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER:
+            case OOM_ADJ_REASON_FINISH_RECEIVER:
                 return UNFREEZE_REASON_FINISH_RECEIVER;
-            case OomAdjuster.OOM_ADJ_REASON_START_RECEIVER:
+            case OOM_ADJ_REASON_START_RECEIVER:
                 return UNFREEZE_REASON_START_RECEIVER;
-            case OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE:
+            case OOM_ADJ_REASON_BIND_SERVICE:
                 return UNFREEZE_REASON_BIND_SERVICE;
-            case OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE:
+            case OOM_ADJ_REASON_UNBIND_SERVICE:
                 return UNFREEZE_REASON_UNBIND_SERVICE;
-            case OomAdjuster.OOM_ADJ_REASON_START_SERVICE:
+            case OOM_ADJ_REASON_START_SERVICE:
                 return UNFREEZE_REASON_START_SERVICE;
-            case OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER:
+            case OOM_ADJ_REASON_GET_PROVIDER:
                 return UNFREEZE_REASON_GET_PROVIDER;
-            case OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER:
+            case OOM_ADJ_REASON_REMOVE_PROVIDER:
                 return UNFREEZE_REASON_REMOVE_PROVIDER;
-            case OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY:
+            case OOM_ADJ_REASON_UI_VISIBILITY:
                 return UNFREEZE_REASON_UI_VISIBILITY;
-            case OomAdjuster.OOM_ADJ_REASON_ALLOWLIST:
+            case OOM_ADJ_REASON_ALLOWLIST:
                 return UNFREEZE_REASON_ALLOWLIST;
-            case OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN:
+            case OOM_ADJ_REASON_PROCESS_BEGIN:
                 return UNFREEZE_REASON_PROCESS_BEGIN;
-            case OomAdjuster.OOM_ADJ_REASON_PROCESS_END:
+            case OOM_ADJ_REASON_PROCESS_END:
                 return UNFREEZE_REASON_PROCESS_END;
+            case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
+                return UNFREEZE_REASON_SHORT_FGS_TIMEOUT;
+            case OOM_ADJ_REASON_SYSTEM_INIT:
+                return UNFREEZE_REASON_SYSTEM_INIT;
+            case OOM_ADJ_REASON_BACKUP:
+                return UNFREEZE_REASON_BACKUP;
+            case OOM_ADJ_REASON_SHELL:
+                return UNFREEZE_REASON_SHELL;
+            case OOM_ADJ_REASON_REMOVE_TASK:
+                return UNFREEZE_REASON_REMOVE_TASK;
+            case OOM_ADJ_REASON_UID_IDLE:
+                return UNFREEZE_REASON_UID_IDLE;
+            case OOM_ADJ_REASON_STOP_SERVICE:
+                return UNFREEZE_REASON_STOP_SERVICE;
+            case OOM_ADJ_REASON_EXECUTING_SERVICE:
+                return UNFREEZE_REASON_EXECUTING_SERVICE;
+            case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+                return UNFREEZE_REASON_RESTRICTION_CHANGE;
+            case OOM_ADJ_REASON_COMPONENT_DISABLED:
+                return UNFREEZE_REASON_COMPONENT_DISABLED;
             default:
                 return UNFREEZE_REASON_NONE;
         }
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 824509a..fe45a95 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -16,6 +16,8 @@
 package com.android.server.am;
 
 import static android.Manifest.permission.GET_ANY_PROVIDER_TYPE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
 import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfile;
 import static android.os.Process.PROC_CHAR;
 import static android.os.Process.PROC_OUT_LONG;
@@ -291,7 +293,7 @@
                     checkTime(startTime, "getContentProviderImpl: before updateOomAdj");
                     final int verifiedAdj = cpr.proc.mState.getVerifiedAdj();
                     boolean success = mService.updateOomAdjLocked(cpr.proc,
-                            OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
+                            OOM_ADJ_REASON_GET_PROVIDER);
                     // XXX things have changed so updateOomAdjLocked doesn't actually tell us
                     // if the process has been successfully adjusted.  So to reduce races with
                     // it, we will check whether the process still exists.  Note that this doesn't
@@ -752,7 +754,7 @@
 
             // update the app's oom adj value and each provider's usage stats
             if (providersPublished) {
-                mService.updateOomAdjLocked(r, OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
+                mService.updateOomAdjLocked(r, OOM_ADJ_REASON_GET_PROVIDER);
                 for (int i = 0, size = providers.size(); i < size; i++) {
                     ContentProviderHolder src = providers.get(i);
                     if (src == null || src.info == null || src.provider == null) {
@@ -830,8 +832,7 @@
             ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, userId);
             if (localCpr.hasExternalProcessHandles()) {
                 if (localCpr.removeExternalProcessHandleLocked(token)) {
-                    mService.updateOomAdjLocked(localCpr.proc,
-                            OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER);
+                    mService.updateOomAdjLocked(localCpr.proc, OOM_ADJ_REASON_REMOVE_PROVIDER);
                 } else {
                     Slog.e(TAG, "Attempt to remove content provider " + localCpr
                             + " with no external reference for token: " + token + ".");
@@ -1500,8 +1501,7 @@
             mService.stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid,
                     cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
             if (updateOomAdj) {
-                mService.updateOomAdjLocked(conn.provider.proc,
-                        OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER);
+                mService.updateOomAdjLocked(conn.provider.proc, OOM_ADJ_REASON_REMOVE_PROVIDER);
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 81b24215..9e9db6a 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -121,10 +121,11 @@
 # Similarly, tags below are used by UserManagerService
 30091 um_user_visibility_changed (userId|1|5),(visible|1)
 
-# Foreground service start/stop events.
+# Foreground service start/stop/denied/timed_out events.
 30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1)
 30101 am_foreground_service_denied (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1)
 30102 am_foreground_service_stop (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1)
+30103 am_foreground_service_timed_out (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1)
 
 # Intent Sender redirect for UserHandle.USER_CURRENT
 30110 am_intent_sender_redirect_user (userId|1|5)
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index a98571b..7847dad 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -41,6 +41,29 @@
 import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
 import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
@@ -101,9 +124,9 @@
 import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 
-import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityThread;
 import android.app.AppProtoEnums;
 import android.app.ApplicationExitInfo;
@@ -141,8 +164,6 @@
 import com.android.server.wm.WindowProcessController;
 
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -154,32 +175,6 @@
 public class OomAdjuster {
     static final String TAG = "OomAdjuster";
 
-    static final int OOM_ADJ_REASON_NONE = 0;
-    static final int OOM_ADJ_REASON_ACTIVITY = 1;
-    static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2;
-    static final int OOM_ADJ_REASON_START_RECEIVER = 3;
-    static final int OOM_ADJ_REASON_BIND_SERVICE = 4;
-    static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5;
-    static final int OOM_ADJ_REASON_START_SERVICE = 6;
-    static final int OOM_ADJ_REASON_GET_PROVIDER = 7;
-    static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8;
-    static final int OOM_ADJ_REASON_UI_VISIBILITY = 9;
-    static final int OOM_ADJ_REASON_ALLOWLIST = 10;
-    static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11;
-    static final int OOM_ADJ_REASON_PROCESS_END = 12;
-    static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13;
-
-    @IntDef(prefix = {"OOM_ADJ_REASON_"},
-            value = {OOM_ADJ_REASON_NONE, OOM_ADJ_REASON_ACTIVITY, OOM_ADJ_REASON_FINISH_RECEIVER,
-                    OOM_ADJ_REASON_START_RECEIVER, OOM_ADJ_REASON_BIND_SERVICE,
-                    OOM_ADJ_REASON_UNBIND_SERVICE, OOM_ADJ_REASON_START_SERVICE,
-                    OOM_ADJ_REASON_GET_PROVIDER, OOM_ADJ_REASON_REMOVE_PROVIDER,
-                    OOM_ADJ_REASON_UI_VISIBILITY, OOM_ADJ_REASON_ALLOWLIST,
-                    OOM_ADJ_REASON_PROCESS_BEGIN, OOM_ADJ_REASON_PROCESS_END,
-                    OOM_ADJ_REASON_SHORT_FGS_TIMEOUT})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface OomAdjReason {}
-
     public static final int oomAdjReasonToProto(@OomAdjReason int oomReason) {
         switch (oomReason) {
             case OOM_ADJ_REASON_NONE:
@@ -210,6 +205,24 @@
                 return AppProtoEnums.OOM_ADJ_REASON_PROCESS_END;
             case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
                 return AppProtoEnums.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+            case OOM_ADJ_REASON_SYSTEM_INIT:
+                return AppProtoEnums.OOM_ADJ_REASON_SYSTEM_INIT;
+            case OOM_ADJ_REASON_BACKUP:
+                return AppProtoEnums.OOM_ADJ_REASON_BACKUP;
+            case OOM_ADJ_REASON_SHELL:
+                return AppProtoEnums.OOM_ADJ_REASON_SHELL;
+            case OOM_ADJ_REASON_REMOVE_TASK:
+                return AppProtoEnums.OOM_ADJ_REASON_REMOVE_TASK;
+            case OOM_ADJ_REASON_UID_IDLE:
+                return AppProtoEnums.OOM_ADJ_REASON_UID_IDLE;
+            case OOM_ADJ_REASON_STOP_SERVICE:
+                return AppProtoEnums.OOM_ADJ_REASON_STOP_SERVICE;
+            case OOM_ADJ_REASON_EXECUTING_SERVICE:
+                return AppProtoEnums.OOM_ADJ_REASON_EXECUTING_SERVICE;
+            case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+                return AppProtoEnums.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+            case OOM_ADJ_REASON_COMPONENT_DISABLED:
+                return AppProtoEnums.OOM_ADJ_REASON_COMPONENT_DISABLED;
             default:
                 return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO;
         }
@@ -246,6 +259,24 @@
                 return OOM_ADJ_REASON_METHOD + "_processEnd";
             case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
                 return OOM_ADJ_REASON_METHOD + "_shortFgs";
+            case OOM_ADJ_REASON_SYSTEM_INIT:
+                return OOM_ADJ_REASON_METHOD + "_systemInit";
+            case OOM_ADJ_REASON_BACKUP:
+                return OOM_ADJ_REASON_METHOD + "_backup";
+            case OOM_ADJ_REASON_SHELL:
+                return OOM_ADJ_REASON_METHOD + "_shell";
+            case OOM_ADJ_REASON_REMOVE_TASK:
+                return OOM_ADJ_REASON_METHOD + "_removeTask";
+            case OOM_ADJ_REASON_UID_IDLE:
+                return OOM_ADJ_REASON_METHOD + "_uidIdle";
+            case OOM_ADJ_REASON_STOP_SERVICE:
+                return OOM_ADJ_REASON_METHOD + "_stopService";
+            case OOM_ADJ_REASON_EXECUTING_SERVICE:
+                return OOM_ADJ_REASON_METHOD + "_executingService";
+            case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+                return OOM_ADJ_REASON_METHOD + "_restrictionChange";
+            case OOM_ADJ_REASON_COMPONENT_DISABLED:
+                return OOM_ADJ_REASON_METHOD + "_componentDisabled";
             default:
                 return "_unknown";
         }
@@ -806,7 +837,7 @@
      */
     @GuardedBy("mService")
     void enqueueOomAdjTargetLocked(ProcessRecord app) {
-        if (app != null) {
+        if (app != null && app.mState.getMaxAdj() > FOREGROUND_APP_ADJ) {
             mPendingProcessSet.add(app);
         }
     }
@@ -874,8 +905,7 @@
     }
 
     @GuardedBy("mService")
-    private void performUpdateOomAdjPendingTargetsLocked(
-            @OomAdjuster.OomAdjReason int oomAdjReason) {
+    private void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
         final ProcessRecord topApp = mService.getTopApp();
 
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
@@ -3453,7 +3483,7 @@
     }
 
     @GuardedBy("mService")
-    void unfreezeTemporarily(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
+    void unfreezeTemporarily(ProcessRecord app, @OomAdjReason int reason) {
         if (!mCachedAppOptimizer.useFreezer()) {
             return;
         }
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index 24cc533..f233107 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import android.app.ActivityManagerInternal.OomAdjReason;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -51,7 +53,7 @@
     /**
      * Last oom adjust change reason for this app.
      */
-    @GuardedBy("mProcLock") private @OomAdjuster.OomAdjReason int mLastOomAdjChangeReason;
+    @GuardedBy("mProcLock") private @OomAdjReason int mLastOomAdjChangeReason;
 
     /**
      * The most recent compaction action performed for this app.
@@ -139,12 +141,12 @@
     }
 
     @GuardedBy("mProcLock")
-    void setLastOomAdjChangeReason(@OomAdjuster.OomAdjReason int reason) {
+    void setLastOomAdjChangeReason(@OomAdjReason int reason) {
         mLastOomAdjChangeReason = reason;
     }
 
     @GuardedBy("mProcLock")
-    @OomAdjuster.OomAdjReason
+    @OomAdjReason
     int getLastOomAdjChangeReason() {
         return mLastOomAdjChangeReason;
     }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b1322ef..a237a07 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -19,6 +19,8 @@
 import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
 import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
 import static android.app.ActivityThread.PROC_START_SEQ_IDENT;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
@@ -2875,7 +2877,7 @@
                     reasonCode, subReason, reason, !doFreeze /* async */);
         }
         killAppZygotesLocked(packageName, appId, userId, false /* force */);
-        mService.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+        mService.updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
         if (doFreeze) {
             freezePackageCgroup(packageUID, false);
         }
@@ -5140,7 +5142,7 @@
                 }
             });
             /* Will be a no-op if nothing pending */
-            mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+            mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_RESTRICTION_CHANGE);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index afae623..7aae4d5 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+
 import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -1450,7 +1452,7 @@
             }
             mService.updateLruProcessLocked(this, activityChange, null /* client */);
             if (updateOomAdj) {
-                mService.updateOomAdjLocked(this, OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+                mService.updateOomAdjLocked(this, OOM_ADJ_REASON_ACTIVITY);
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 71d5d39..ab71acd 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
 import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_ACTIVITY;
@@ -613,7 +614,7 @@
     void forceProcessStateUpTo(int newState) {
         if (mRepProcState > newState) {
             synchronized (mProcLock) {
-                mRepProcState = newState;
+                setReportedProcState(newState);
                 setCurProcState(newState);
                 setCurRawProcState(newState);
             }
@@ -766,7 +767,7 @@
             Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation
                     + " for pid=" + mApp.getPid());
         }
-        mService.updateOomAdjLocked(mApp, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+        mService.updateOomAdjLocked(mApp, OOM_ADJ_REASON_UI_VISIBILITY);
     }
 
     @GuardedBy({"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/am/ProviderMap.java b/services/core/java/com/android/server/am/ProviderMap.java
index 072eba5..20c695f 100644
--- a/services/core/java/com/android/server/am/ProviderMap.java
+++ b/services/core/java/com/android/server/am/ProviderMap.java
@@ -351,21 +351,26 @@
 
     protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args,
             int opti, boolean dumpAll) {
-        ArrayList<ContentProviderRecord> providers = getProvidersForName(name);
+        try {
+            mAm.mOomAdjuster.mCachedAppOptimizer.enableFreezer(false);
+            ArrayList<ContentProviderRecord> providers = getProvidersForName(name);
 
-        if (providers.size() <= 0) {
-            return false;
-        }
-
-        boolean needSep = false;
-        for (int i=0; i<providers.size(); i++) {
-            if (needSep) {
-                pw.println();
+            if (providers.size() <= 0) {
+                return false;
             }
-            needSep = true;
-            dumpProvider("", fd, pw, providers.get(i), args, dumpAll);
+
+            boolean needSep = false;
+            for (int i=0; i<providers.size(); i++) {
+                if (needSep) {
+                    pw.println();
+                }
+                needSep = true;
+                dumpProvider("", fd, pw, providers.get(i), args, dumpAll);
+            }
+            return true;
+        } finally {
+            mAm.mOomAdjuster.mCachedAppOptimizer.enableFreezer(true);
         }
-        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 60a7f93..8c227f5 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -83,6 +83,7 @@
         DeviceConfig.NAMESPACE_CAMERA_NATIVE,
         DeviceConfig.NAMESPACE_CONFIGURATION,
         DeviceConfig.NAMESPACE_CONNECTIVITY,
+        DeviceConfig.NAMESPACE_EDGETPU_NATIVE,
         DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS,
         DeviceConfig.NAMESPACE_LMKD_NATIVE,
@@ -99,7 +100,6 @@
         DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_SWCODEC_NATIVE,
-        DeviceConfig.NAMESPACE_TETHERING,
         DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE,
         DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE,
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 0be69ce..903c2ba 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -34,6 +34,7 @@
 import static android.app.AppOpsManager.MODE_FOREGROUND;
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
 import static android.app.AppOpsManager.OP_FLAG_SELF;
 import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
@@ -42,6 +43,7 @@
 import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO_SANDBOXED;
 import static android.app.AppOpsManager.OP_VIBRATE;
 import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
 import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
@@ -3159,17 +3161,29 @@
                     packageName);
         }
 
-        // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
-        // purposes and not as a check, also make sure that the caller is allowed to access
-        // the data gated by OP_RECORD_AUDIO.
+        // As a special case for OP_RECORD_AUDIO_HOTWORD, OP_RECEIVE_AMBIENT_TRIGGER_AUDIO and
+        // OP_RECORD_AUDIO_SANDBOXED which we use only for attribution purposes and not as a check,
+        // also make sure that the caller is allowed to access the data gated by OP_RECORD_AUDIO.
         //
         // TODO: Revert this change before Android 12.
-        if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
-            int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
+        int result = MODE_DEFAULT;
+        if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO
+                || code == OP_RECORD_AUDIO_SANDBOXED) {
+            result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
+            // Check result
             if (result != AppOpsManager.MODE_ALLOWED) {
                 return new SyncNotedAppOp(result, code, attributionTag, packageName);
             }
         }
+        // As a special case for OP_CAMERA_SANDBOXED.
+        if (code == OP_CAMERA_SANDBOXED) {
+            result = checkOperation(OP_CAMERA, uid, packageName);
+            // Check result
+            if (result != AppOpsManager.MODE_ALLOWED) {
+                return new SyncNotedAppOp(result, code, attributionTag, packageName);
+            }
+        }
+
         return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
                 Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
                 shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 7c8e6df..5127d26 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -19,6 +19,8 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
 
@@ -519,6 +521,9 @@
 
         try {
             mStatusBarService.onBiometricHelp(sensorIdToModality(sensorId), message);
+            final int aAcquiredInfo = acquiredInfo == FINGERPRINT_ACQUIRED_VENDOR
+                    ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquiredInfo;
+            mClientReceiver.onAcquired(aAcquiredInfo, message);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
         }
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensor.java b/services/core/java/com/android/server/biometrics/BiometricSensor.java
index 937e3f8..bac4480 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensor.java
+++ b/services/core/java/com/android/server/biometrics/BiometricSensor.java
@@ -22,14 +22,20 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IBiometricSensorReceiver;
+import android.hardware.biometrics.SensorPropertiesInternal;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Wraps IBiometricAuthenticator implementation and stores information about the authenticator,
@@ -67,6 +73,7 @@
     public final int id;
     public final @Authenticators.Types int oemStrength; // strength as configured by the OEM
     public final int modality;
+    @NonNull public final List<ComponentInfoInternal> componentInfo;
     public final IBiometricAuthenticator impl;
 
     private @Authenticators.Types int mUpdatedStrength; // updated by BiometricStrengthController
@@ -86,15 +93,16 @@
      */
     abstract boolean confirmationSupported();
 
-    BiometricSensor(@NonNull Context context, int id, int modality,
-            @Authenticators.Types int strength, IBiometricAuthenticator impl) {
+    BiometricSensor(@NonNull Context context, int modality, @NonNull SensorPropertiesInternal props,
+            IBiometricAuthenticator impl) {
         this.mContext = context;
-        this.id = id;
+        this.id = props.sensorId;
         this.modality = modality;
-        this.oemStrength = strength;
+        this.oemStrength = Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
+        this.componentInfo = Collections.unmodifiableList(props.componentInfo);
         this.impl = impl;
 
-        mUpdatedStrength = strength;
+        mUpdatedStrength = oemStrength;
         goToStateUnknown();
     }
 
@@ -178,8 +186,25 @@
         return "ID(" + id + ")"
                 + ", oemStrength: " + oemStrength
                 + ", updatedStrength: " + mUpdatedStrength
-                + ", modality " + modality
+                + ", modality: " + modality
                 + ", state: " + mSensorState
                 + ", cookie: " + mCookie;
     }
+
+    protected void dump(@NonNull IndentingPrintWriter pw) {
+        pw.println(TextUtils.formatSimple("ID: %d", id));
+        pw.increaseIndent();
+        pw.println(TextUtils.formatSimple("oemStrength: %d", oemStrength));
+        pw.println(TextUtils.formatSimple("updatedStrength: %d", mUpdatedStrength));
+        pw.println(TextUtils.formatSimple("modality: %d", modality));
+        pw.println("componentInfo:");
+        for (ComponentInfoInternal info : componentInfo) {
+            pw.increaseIndent();
+            info.dump(pw);
+            pw.decreaseIndent();
+        }
+        pw.println(TextUtils.formatSimple("state: %d", mSensorState));
+        pw.println(TextUtils.formatSimple("cookie: %d", mCookie));
+        pw.decreaseIndent();
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index ffa5d20..f44d14b 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -62,6 +62,7 @@
 import android.security.KeyStore;
 import android.text.TextUtils;
 import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
@@ -638,13 +639,16 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
-        public synchronized void registerAuthenticator(int id, int modality,
-                @Authenticators.Types int strength,
+        public synchronized void registerAuthenticator(int modality,
+                @NonNull SensorPropertiesInternal props,
                 @NonNull IBiometricAuthenticator authenticator) {
 
             super.registerAuthenticator_enforcePermission();
 
-            Slog.d(TAG, "Registering ID: " + id
+            @Authenticators.Types final int strength =
+                    Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
+
+            Slog.d(TAG, "Registering ID: " + props.sensorId
                     + " Modality: " + modality
                     + " Strength: " + strength);
 
@@ -665,12 +669,12 @@
             }
 
             for (BiometricSensor sensor : mSensors) {
-                if (sensor.id == id) {
+                if (sensor.id == props.sensorId) {
                     throw new IllegalStateException("Cannot register duplicate authenticator");
                 }
             }
 
-            mSensors.add(new BiometricSensor(getContext(), id, modality, strength, authenticator) {
+            mSensors.add(new BiometricSensor(getContext(), modality, props, authenticator) {
                 @Override
                 boolean confirmationAlwaysRequired(int userId) {
                     return mSettingObserver.getConfirmationAlwaysRequired(modality, userId);
@@ -1360,13 +1364,17 @@
         return null;
     }
 
-    private void dumpInternal(PrintWriter pw) {
+    private void dumpInternal(PrintWriter printWriter) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter);
+
         pw.println("Legacy Settings: " + mSettingObserver.mUseLegacyFaceOnlySettings);
         pw.println();
 
         pw.println("Sensors:");
         for (BiometricSensor sensor : mSensors) {
-            pw.println(" " + sensor);
+            pw.increaseIndent();
+            sensor.dump(pw);
+            pw.decreaseIndent();
         }
         pw.println();
         pw.println("CurrentSession: " + mAuthSession);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
index 0f0a81d..d43045b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
@@ -20,7 +20,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
@@ -28,7 +27,6 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
-import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.BiometricServiceRegistry;
 
 import java.util.List;
@@ -53,10 +51,8 @@
     @Override
     protected void registerService(@NonNull IBiometricService service,
             @NonNull FaceSensorPropertiesInternal props) {
-        @BiometricManager.Authenticators.Types final int strength =
-                Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
         try {
-            service.registerAuthenticator(props.sensorId, TYPE_FACE, strength,
+            service.registerAuthenticator(TYPE_FACE, props,
                     new FaceAuthenticator(mService, props.sensorId));
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 128ef0b..6c26e2b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -413,6 +413,11 @@
                                 Slog.e(TAG, "Remote exception in onAuthenticationAcquired()", e);
                             }
                         }
+
+                        @Override
+                        public void onAuthenticationHelp(int acquireInfo, CharSequence helpString) {
+                            onAuthenticationAcquired(acquireInfo);
+                        }
                     };
 
             return biometricPrompt.authenticateForOperation(
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
index 33810b7..6d210ea 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
@@ -20,7 +20,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
@@ -28,7 +27,6 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
-import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.BiometricServiceRegistry;
 
 import java.util.List;
@@ -53,10 +51,8 @@
     @Override
     protected void registerService(@NonNull IBiometricService service,
             @NonNull FingerprintSensorPropertiesInternal props) {
-        @BiometricManager.Authenticators.Types final int strength =
-                Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
         try {
-            service.registerAuthenticator(props.sensorId, TYPE_FINGERPRINT, strength,
+            service.registerAuthenticator(TYPE_FINGERPRINT, props,
                     new FingerprintAuthenticator(mService, props.sensorId));
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
index 35ea36c..f27761f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
@@ -16,12 +16,10 @@
 
 package com.android.server.biometrics.sensors.iris;
 
-import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.hardware.iris.IIrisService;
@@ -33,7 +31,6 @@
 
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
-import com.android.server.biometrics.Utils;
 
 import java.util.List;
 
@@ -75,17 +72,12 @@
                         ServiceManager.getService(Context.BIOMETRIC_SERVICE));
 
                 for (SensorPropertiesInternal hidlSensor : hidlSensors) {
-                    final int sensorId = hidlSensor.sensorId;
-                    final @BiometricManager.Authenticators.Types int strength =
-                            Utils.propertyStrengthToAuthenticatorStrength(
-                                    hidlSensor.sensorStrength);
-                    final IrisAuthenticator authenticator = new IrisAuthenticator(mServiceWrapper,
-                            sensorId);
                     try {
-                        biometricService.registerAuthenticator(sensorId, TYPE_IRIS, strength,
-                                authenticator);
+                        biometricService.registerAuthenticator(TYPE_IRIS, hidlSensor,
+                                new IrisAuthenticator(mServiceWrapper, hidlSensor.sensorId));
                     } catch (RemoteException e) {
-                        Slog.e(TAG, "Remote exception when registering sensorId: " + sensorId);
+                        Slog.e(TAG, "Remote exception when registering sensorId: "
+                                + hidlSensor.sensorId);
                     }
                 }
             });
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index b25206d..7e48f68 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3391,6 +3391,7 @@
          * consistency of the Ikev2VpnRunner fields.
          */
         public void onDefaultNetworkChanged(@NonNull Network network) {
+            mEventChanges.log("[UnderlyingNW] Default network changed to " + network);
             Log.d(TAG, "onDefaultNetworkChanged: " + network);
 
             // If there is a new default network brought up, cancel the retry task to prevent
@@ -3628,6 +3629,7 @@
                 mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities)
                         .setTransportInfo(info)
                         .build();
+                mEventChanges.log("[VPNRunner] Update agent caps " + mNetworkCapabilities);
                 doSendNetworkCapabilities(mNetworkAgent, mNetworkCapabilities);
             }
         }
@@ -3664,6 +3666,7 @@
 
         private void startIkeSession(@NonNull Network underlyingNetwork) {
             Log.d(TAG, "Start new IKE session on network " + underlyingNetwork);
+            mEventChanges.log("[IKE] Start IKE session over " + underlyingNetwork);
 
             try {
                 // Clear mInterface to prevent Ikev2VpnRunner being cleared when
@@ -3778,6 +3781,7 @@
         }
 
         public void onValidationStatus(int status) {
+            mEventChanges.log("[Validation] validation status " + status);
             if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
                 // No data stall now. Reset it.
                 mExecutor.execute(() -> {
@@ -3818,6 +3822,7 @@
          * consistency of the Ikev2VpnRunner fields.
          */
         public void onDefaultNetworkLost(@NonNull Network network) {
+            mEventChanges.log("[UnderlyingNW] Network lost " + network);
             // If the default network is torn down, there is no need to call
             // startOrMigrateIkeSession() since it will always check if there is an active network
             // can be used or not.
@@ -3936,6 +3941,8 @@
          * consistency of the Ikev2VpnRunner fields.
          */
         public void onSessionLost(int token, @Nullable Exception exception) {
+            mEventChanges.log("[IKE] Session lost on network " + mActiveNetwork
+                    + (null == exception ? "" : " reason " + exception.getMessage()));
             Log.d(TAG, "onSessionLost() called for token " + token);
 
             if (!isActiveToken(token)) {
@@ -4092,6 +4099,7 @@
          * consistency of the Ikev2VpnRunner fields.
          */
         private void disconnectVpnRunner() {
+            mEventChanges.log("[VPNRunner] Disconnect runner, underlying network" + mActiveNetwork);
             mActiveNetwork = null;
             mUnderlyingNetworkCapabilities = null;
             mUnderlyingLinkProperties = null;
diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java
index 067a2d6..cfdcd63 100644
--- a/services/core/java/com/android/server/display/BrightnessThrottler.java
+++ b/services/core/java/com/android/server/display/BrightnessThrottler.java
@@ -36,8 +36,8 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData;
-import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -69,12 +69,12 @@
 
     // Maps the throttling ID to the data. Sourced from DisplayDeviceConfig.
     @NonNull
-    private HashMap<String, BrightnessThrottlingData> mDdcThrottlingDataMap;
+    private HashMap<String, ThermalBrightnessThrottlingData> mDdcThermalThrottlingDataMap;
 
     // Current throttling data being used.
     // Null if we do not support throttling.
     @Nullable
-    private BrightnessThrottlingData mThrottlingData;
+    private ThermalBrightnessThrottlingData mThermalThrottlingData;
 
     private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
     private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason =
@@ -82,51 +82,53 @@
     private String mUniqueDisplayId;
 
     // The most recent string that has been set from DeviceConfig
-    private String mBrightnessThrottlingDataString;
+    private String mThermalBrightnessThrottlingDataString;
 
     // The brightness throttling configuration that should be used.
-    private String mBrightnessThrottlingDataId;
+    private String mThermalBrightnessThrottlingDataId;
 
     // This is a collection of brightness throttling data that has been written as overrides from
     // the DeviceConfig. This will always take priority over the display device config data.
     // We need to store the data for every display device, so we do not need to update this each
     // time the underlying display device changes.
     // This map is indexed by uniqueDisplayId, to provide maps for throttlingId -> throttlingData.
-    // HashMap< uniqueDisplayId, HashMap< throttlingDataId, BrightnessThrottlingData >>
-    private final HashMap<String, HashMap<String, BrightnessThrottlingData>>
-            mBrightnessThrottlingDataOverride = new HashMap<>(1);
+    // HashMap< uniqueDisplayId, HashMap< throttlingDataId, ThermalBrightnessThrottlingData >>
+    private final HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>>
+            mThermalBrightnessThrottlingDataOverride = new HashMap<>(1);
 
     BrightnessThrottler(Handler handler, Runnable throttlingChangeCallback, String uniqueDisplayId,
             String throttlingDataId,
-            @NonNull HashMap<String, BrightnessThrottlingData> brightnessThrottlingDataMap) {
+            @NonNull HashMap<String, ThermalBrightnessThrottlingData>
+                    thermalBrightnessThrottlingDataMap) {
         this(new Injector(), handler, handler, throttlingChangeCallback,
-                uniqueDisplayId, throttlingDataId, brightnessThrottlingDataMap);
+                uniqueDisplayId, throttlingDataId, thermalBrightnessThrottlingDataMap);
     }
 
     @VisibleForTesting
     BrightnessThrottler(Injector injector, Handler handler, Handler deviceConfigHandler,
             Runnable throttlingChangeCallback, String uniqueDisplayId, String throttlingDataId,
-            @NonNull HashMap<String, BrightnessThrottlingData> brightnessThrottlingDataMap) {
+            @NonNull HashMap<String, ThermalBrightnessThrottlingData>
+                    thermalBrightnessThrottlingDataMap) {
         mInjector = injector;
 
         mHandler = handler;
         mDeviceConfigHandler = deviceConfigHandler;
-        mDdcThrottlingDataMap = brightnessThrottlingDataMap;
+        mDdcThermalThrottlingDataMap = thermalBrightnessThrottlingDataMap;
         mThrottlingChangeCallback = throttlingChangeCallback;
         mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
 
         mUniqueDisplayId = uniqueDisplayId;
         mDeviceConfig = injector.getDeviceConfig();
         mDeviceConfigListener = new DeviceConfigListener();
-        mBrightnessThrottlingDataId = throttlingDataId;
-        mDdcThrottlingDataMap = brightnessThrottlingDataMap;
-        loadBrightnessThrottlingDataFromDeviceConfig();
-        loadBrightnessThrottlingDataFromDisplayDeviceConfig(mDdcThrottlingDataMap,
-                mBrightnessThrottlingDataId, mUniqueDisplayId);
+        mThermalBrightnessThrottlingDataId = throttlingDataId;
+        mDdcThermalThrottlingDataMap = thermalBrightnessThrottlingDataMap;
+        loadThermalBrightnessThrottlingDataFromDeviceConfig();
+        loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(mDdcThermalThrottlingDataMap,
+                mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
     }
 
     boolean deviceSupportsThrottling() {
-        return mThrottlingData != null;
+        return mThermalThrottlingData != null;
     }
 
     float getBrightnessCap() {
@@ -154,14 +156,14 @@
         mThrottlingStatus = THROTTLING_INVALID;
     }
 
-    void loadBrightnessThrottlingDataFromDisplayDeviceConfig(
-            HashMap<String, BrightnessThrottlingData> ddcThrottlingDataMap,
+    void loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
+            HashMap<String, ThermalBrightnessThrottlingData> ddcThrottlingDataMap,
             String brightnessThrottlingDataId,
             String uniqueDisplayId) {
-        mDdcThrottlingDataMap = ddcThrottlingDataMap;
-        mBrightnessThrottlingDataId = brightnessThrottlingDataId;
+        mDdcThermalThrottlingDataMap = ddcThrottlingDataMap;
+        mThermalBrightnessThrottlingDataId = brightnessThrottlingDataId;
         mUniqueDisplayId = uniqueDisplayId;
-        resetThrottlingData();
+        resetThermalThrottlingData();
     }
 
     private float verifyAndConstrainBrightnessCap(float brightness) {
@@ -183,11 +185,11 @@
     private void thermalStatusChanged(@Temperature.ThrottlingStatus int newStatus) {
         if (mThrottlingStatus != newStatus) {
             mThrottlingStatus = newStatus;
-            updateThrottling();
+            updateThermalThrottling();
         }
     }
 
-    private void updateThrottling() {
+    private void updateThermalThrottling() {
         if (!deviceSupportsThrottling()) {
             return;
         }
@@ -195,9 +197,9 @@
         float brightnessCap = PowerManager.BRIGHTNESS_MAX;
         int brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
 
-        if (mThrottlingStatus != THROTTLING_INVALID && mThrottlingData != null) {
+        if (mThrottlingStatus != THROTTLING_INVALID && mThermalThrottlingData != null) {
             // Throttling levels are sorted by increasing severity
-            for (ThrottlingLevel level : mThrottlingData.throttlingLevels) {
+            for (ThrottlingLevel level : mThermalThrottlingData.throttlingLevels) {
                 if (level.thermalStatus <= mThrottlingStatus) {
                     brightnessCap = level.brightness;
                     brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
@@ -230,21 +232,23 @@
 
     private void dumpLocal(PrintWriter pw) {
         pw.println("BrightnessThrottler:");
-        pw.println("  mBrightnessThrottlingDataId=" + mBrightnessThrottlingDataId);
-        pw.println("  mThrottlingData=" + mThrottlingData);
+        pw.println("  mThermalBrightnessThrottlingDataId=" + mThermalBrightnessThrottlingDataId);
+        pw.println("  mThermalThrottlingData=" + mThermalThrottlingData);
         pw.println("  mUniqueDisplayId=" + mUniqueDisplayId);
         pw.println("  mThrottlingStatus=" + mThrottlingStatus);
         pw.println("  mBrightnessCap=" + mBrightnessCap);
         pw.println("  mBrightnessMaxReason=" +
             BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason));
-        pw.println("  mDdcThrottlingDataMap=" + mDdcThrottlingDataMap);
-        pw.println("  mBrightnessThrottlingDataOverride=" + mBrightnessThrottlingDataOverride);
-        pw.println("  mBrightnessThrottlingDataString=" + mBrightnessThrottlingDataString);
+        pw.println("  mDdcThermalThrottlingDataMap=" + mDdcThermalThrottlingDataMap);
+        pw.println("  mThermalBrightnessThrottlingDataOverride="
+                + mThermalBrightnessThrottlingDataOverride);
+        pw.println("  mThermalBrightnessThrottlingDataString="
+                + mThermalBrightnessThrottlingDataString);
 
         mSkinThermalStatusObserver.dump(pw);
     }
 
-    private String getBrightnessThrottlingDataString() {
+    private String getThermalBrightnessThrottlingDataString() {
         return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
                 DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA,
                 /* defaultValue= */ null);
@@ -260,7 +264,7 @@
     // displayId, number, <state, val> * number
     // displayId, <number, <state, val> * number>, throttlingId
     private boolean parseAndAddData(@NonNull String strArray,
-            @NonNull HashMap<String, HashMap<String, BrightnessThrottlingData>>
+            @NonNull HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>>
                     displayIdToThrottlingIdToBtd) {
         boolean validConfig = true;
         String[] items = strArray.split(",");
@@ -282,11 +286,11 @@
             }
 
             String throttlingDataId = (i < items.length) ? items[i++] : DEFAULT_ID;
-            BrightnessThrottlingData throttlingLevelsData =
-                    DisplayDeviceConfig.BrightnessThrottlingData.create(throttlingLevels);
+            ThermalBrightnessThrottlingData throttlingLevelsData =
+                    DisplayDeviceConfig.ThermalBrightnessThrottlingData.create(throttlingLevels);
 
             // Add throttlingLevelsData to inner map where necessary.
-            HashMap<String, BrightnessThrottlingData> throttlingMapForDisplay =
+            HashMap<String, ThermalBrightnessThrottlingData> throttlingMapForDisplay =
                     displayIdToThrottlingIdToBtd.get(uniqueDisplayId);
             if (throttlingMapForDisplay == null) {
                 throttlingMapForDisplay = new HashMap<>();
@@ -311,14 +315,14 @@
         return validConfig;
     }
 
-    private void loadBrightnessThrottlingDataFromDeviceConfig() {
-        HashMap<String, HashMap<String, BrightnessThrottlingData>> tempThrottlingData =
+    private void loadThermalBrightnessThrottlingDataFromDeviceConfig() {
+        HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>> tempThrottlingData =
                 new HashMap<>(1);
-        mBrightnessThrottlingDataString = getBrightnessThrottlingDataString();
+        mThermalBrightnessThrottlingDataString = getThermalBrightnessThrottlingDataString();
         boolean validConfig = true;
-        mBrightnessThrottlingDataOverride.clear();
-        if (mBrightnessThrottlingDataString != null) {
-            String[] throttlingDataSplits = mBrightnessThrottlingDataString.split(";");
+        mThermalBrightnessThrottlingDataOverride.clear();
+        if (mThermalBrightnessThrottlingDataString != null) {
+            String[] throttlingDataSplits = mThermalBrightnessThrottlingDataString.split(";");
             for (String s : throttlingDataSplits) {
                 if (!parseAndAddData(s, tempThrottlingData)) {
                     validConfig = false;
@@ -327,26 +331,27 @@
             }
 
             if (validConfig) {
-                mBrightnessThrottlingDataOverride.putAll(tempThrottlingData);
+                mThermalBrightnessThrottlingDataOverride.putAll(tempThrottlingData);
                 tempThrottlingData.clear();
             }
 
         } else {
-            Slog.w(TAG, "DeviceConfig BrightnessThrottlingData is null");
+            Slog.w(TAG, "DeviceConfig ThermalBrightnessThrottlingData is null");
         }
     }
 
-    private void resetThrottlingData() {
+    private void resetThermalThrottlingData() {
         stop();
 
         mDeviceConfigListener.startListening();
 
         // Get throttling data for this id, if it exists
-        mThrottlingData = getConfigFromId(mBrightnessThrottlingDataId);
+        mThermalThrottlingData = getConfigFromId(mThermalBrightnessThrottlingDataId);
 
         // Fallback to default id otherwise.
-        if (!DEFAULT_ID.equals(mBrightnessThrottlingDataId) && mThrottlingData == null) {
-            mThrottlingData = getConfigFromId(DEFAULT_ID);
+        if (!DEFAULT_ID.equals(mThermalBrightnessThrottlingDataId)
+                && mThermalThrottlingData == null) {
+            mThermalThrottlingData = getConfigFromId(DEFAULT_ID);
             Slog.d(TAG, "Falling back to default throttling Id");
         }
 
@@ -355,17 +360,17 @@
         }
     }
 
-    private BrightnessThrottlingData getConfigFromId(String id) {
-        BrightnessThrottlingData returnValue;
+    private ThermalBrightnessThrottlingData getConfigFromId(String id) {
+        ThermalBrightnessThrottlingData returnValue;
 
         // Fallback pattern for fetching correct throttling data for this display and id.
         // 1) throttling data from device config for this throttling data id
-        returnValue =  mBrightnessThrottlingDataOverride.get(mUniqueDisplayId) == null
+        returnValue =  mThermalBrightnessThrottlingDataOverride.get(mUniqueDisplayId) == null
                 ? null
-                : mBrightnessThrottlingDataOverride.get(mUniqueDisplayId).get(id);
+                : mThermalBrightnessThrottlingDataOverride.get(mUniqueDisplayId).get(id);
         // 2) throttling data from ddc for this throttling data id
         returnValue = returnValue == null
-                ? mDdcThrottlingDataMap.get(id)
+                ? mDdcThermalThrottlingDataMap.get(id)
                 : returnValue;
 
         return returnValue;
@@ -391,8 +396,8 @@
 
         @Override
         public void onPropertiesChanged(DeviceConfig.Properties properties) {
-            loadBrightnessThrottlingDataFromDeviceConfig();
-            resetThrottlingData();
+            loadThermalBrightnessThrottlingDataFromDeviceConfig();
+            resetThermalThrottlingData();
         }
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index da02115..ca482dc 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -687,8 +687,8 @@
     private int[] mHighDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
     private int[] mHighAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
 
-    private final HashMap<String, BrightnessThrottlingData>
-            mBrightnessThrottlingDataMapByThrottlingId = new HashMap<>();
+    private final HashMap<String, ThermalBrightnessThrottlingData>
+            mThermalBrightnessThrottlingDataMapByThrottlingId = new HashMap<>();
 
     private final Map<String, SparseArray<SurfaceControl.RefreshRateRange>>
             mRefreshRateThrottlingMap = new HashMap<>();
@@ -1348,9 +1348,9 @@
     /**
      * @return brightness throttling configuration data for this display, for each throttling id.
      */
-    public HashMap<String, BrightnessThrottlingData>
-            getBrightnessThrottlingDataMapByThrottlingId() {
-        return mBrightnessThrottlingDataMapByThrottlingId;
+    public HashMap<String, ThermalBrightnessThrottlingData>
+            getThermalBrightnessThrottlingDataMapByThrottlingId() {
+        return mThermalBrightnessThrottlingDataMapByThrottlingId;
     }
 
     /**
@@ -1358,7 +1358,7 @@
      * @return refresh rate throttling configuration
      */
     @Nullable
-    public SparseArray<SurfaceControl.RefreshRateRange> getRefreshRateThrottlingData(
+    public SparseArray<SurfaceControl.RefreshRateRange> getThermalRefreshRateThrottlingData(
             @Nullable String id) {
         String key = id == null ? DEFAULT_ID : id;
         return mRefreshRateThrottlingMap.get(key);
@@ -1525,8 +1525,8 @@
                 + ", isHbmEnabled=" + mIsHighBrightnessModeEnabled
                 + ", mHbmData=" + mHbmData
                 + ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
-                + ", mBrightnessThrottlingDataMapByThrottlingId="
-                + mBrightnessThrottlingDataMapByThrottlingId
+                + ", mThermalBrightnessThrottlingDataMapByThrottlingId="
+                + mThermalBrightnessThrottlingDataMapByThrottlingId
                 + "\n"
                 + ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease
                 + ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease
@@ -1887,11 +1887,11 @@
             Slog.i(TAG, "No thermal throttling config found");
             return;
         }
-        loadBrightnessThrottlingMaps(throttlingConfig);
-        loadRefreshRateThermalThrottlingMap(throttlingConfig);
+        loadThermalBrightnessThrottlingMaps(throttlingConfig);
+        loadThermalRefreshRateThrottlingMap(throttlingConfig);
     }
 
-    private void loadBrightnessThrottlingMaps(ThermalThrottling throttlingConfig) {
+    private void loadThermalBrightnessThrottlingMaps(ThermalThrottling throttlingConfig) {
         final List<BrightnessThrottlingMap> maps = throttlingConfig.getBrightnessThrottlingMap();
         if (maps == null || maps.isEmpty()) {
             Slog.i(TAG, "No brightness throttling map found");
@@ -1901,7 +1901,7 @@
         for (BrightnessThrottlingMap map : maps) {
             final List<BrightnessThrottlingPoint> points = map.getBrightnessThrottlingPoint();
             // At least 1 point is guaranteed by the display device config schema
-            List<BrightnessThrottlingData.ThrottlingLevel> throttlingLevels =
+            List<ThermalBrightnessThrottlingData.ThrottlingLevel> throttlingLevels =
                     new ArrayList<>(points.size());
 
             boolean badConfig = false;
@@ -1912,24 +1912,24 @@
                     break;
                 }
 
-                throttlingLevels.add(new BrightnessThrottlingData.ThrottlingLevel(
+                throttlingLevels.add(new ThermalBrightnessThrottlingData.ThrottlingLevel(
                         convertThermalStatus(status), point.getBrightness().floatValue()));
             }
 
             if (!badConfig) {
                 String id = map.getId() == null ? DEFAULT_ID
                         : map.getId();
-                if (mBrightnessThrottlingDataMapByThrottlingId.containsKey(id)) {
+                if (mThermalBrightnessThrottlingDataMapByThrottlingId.containsKey(id)) {
                     throw new RuntimeException("Brightness throttling data with ID " + id
                             + " already exists");
                 }
-                mBrightnessThrottlingDataMapByThrottlingId.put(id,
-                        BrightnessThrottlingData.create(throttlingLevels));
+                mThermalBrightnessThrottlingDataMapByThrottlingId.put(id,
+                        ThermalBrightnessThrottlingData.create(throttlingLevels));
             }
         }
     }
 
-    private void loadRefreshRateThermalThrottlingMap(ThermalThrottling throttlingConfig) {
+    private void loadThermalRefreshRateThrottlingMap(ThermalThrottling throttlingConfig) {
         List<RefreshRateThrottlingMap> maps = throttlingConfig.getRefreshRateThrottlingMap();
         if (maps == null || maps.isEmpty()) {
             Slog.w(TAG, "RefreshRateThrottling: map not found");
@@ -2396,7 +2396,6 @@
             mHbmData.timeWindowMillis = hbmTiming.getTimeWindowSecs_all().longValue() * 1000;
             mHbmData.timeMaxMillis = hbmTiming.getTimeMaxSecs_all().longValue() * 1000;
             mHbmData.timeMinMillis = hbmTiming.getTimeMinSecs_all().longValue() * 1000;
-            mHbmData.thermalStatusLimit = convertThermalStatus(hbm.getThermalStatusLimit_all());
             mHbmData.allowInLowPowerMode = hbm.getAllowInLowPowerMode_all();
             final RefreshRateRange rr = hbm.getRefreshRate_all();
             if (rr != null) {
@@ -2972,9 +2971,6 @@
         /** Brightness level at which we transition from normal to high-brightness. */
         public float transitionPoint;
 
-        /** Enable HBM only if the thermal status is not higher than this. */
-        public @PowerManager.ThermalStatus int thermalStatusLimit;
-
         /** Whether HBM is allowed when {@code Settings.Global.LOW_POWER_MODE} is active. */
         public boolean allowInLowPowerMode;
 
@@ -2993,15 +2989,13 @@
         HighBrightnessModeData() {}
 
         HighBrightnessModeData(float minimumLux, float transitionPoint, long timeWindowMillis,
-                long timeMaxMillis, long timeMinMillis,
-                @PowerManager.ThermalStatus int thermalStatusLimit, boolean allowInLowPowerMode,
+                long timeMaxMillis, long timeMinMillis, boolean allowInLowPowerMode,
                 float minimumHdrPercentOfScreen) {
             this.minimumLux = minimumLux;
             this.transitionPoint = transitionPoint;
             this.timeWindowMillis = timeWindowMillis;
             this.timeMaxMillis = timeMaxMillis;
             this.timeMinMillis = timeMinMillis;
-            this.thermalStatusLimit = thermalStatusLimit;
             this.allowInLowPowerMode = allowInLowPowerMode;
             this.minimumHdrPercentOfScreen = minimumHdrPercentOfScreen;
         }
@@ -3016,7 +3010,6 @@
             other.timeMaxMillis = timeMaxMillis;
             other.timeMinMillis = timeMinMillis;
             other.transitionPoint = transitionPoint;
-            other.thermalStatusLimit = thermalStatusLimit;
             other.allowInLowPowerMode = allowInLowPowerMode;
             other.minimumHdrPercentOfScreen = minimumHdrPercentOfScreen;
         }
@@ -3029,7 +3022,6 @@
                     + ", timeWindow: " + timeWindowMillis + "ms"
                     + ", timeMax: " + timeMaxMillis + "ms"
                     + ", timeMin: " + timeMinMillis + "ms"
-                    + ", thermalStatusLimit: " + thermalStatusLimit
                     + ", allowInLowPowerMode: " + allowInLowPowerMode
                     + ", minimumHdrPercentOfScreen: " + minimumHdrPercentOfScreen
                     + "} ";
@@ -3039,7 +3031,7 @@
     /**
      * Container for brightness throttling data.
      */
-    public static class BrightnessThrottlingData {
+    public static class ThermalBrightnessThrottlingData {
         public List<ThrottlingLevel> throttlingLevels;
 
         static class ThrottlingLevel {
@@ -3080,7 +3072,8 @@
         /**
          * Creates multiple temperature based throttling levels of brightness
          */
-        public static BrightnessThrottlingData create(List<ThrottlingLevel> throttlingLevels) {
+        public static ThermalBrightnessThrottlingData create(
+                List<ThrottlingLevel> throttlingLevels) {
             if (throttlingLevels == null || throttlingLevels.size() == 0) {
                 Slog.e(TAG, "BrightnessThrottlingData received null or empty throttling levels");
                 return null;
@@ -3118,12 +3111,12 @@
                 }
             }
 
-            return new BrightnessThrottlingData(throttlingLevels);
+            return new ThermalBrightnessThrottlingData(throttlingLevels);
         }
 
         @Override
         public String toString() {
-            return "BrightnessThrottlingData{"
+            return "ThermalBrightnessThrottlingData{"
                     + "throttlingLevels:" + throttlingLevels
                     + "} ";
         }
@@ -3134,12 +3127,12 @@
                 return true;
             }
 
-            if (!(obj instanceof BrightnessThrottlingData)) {
+            if (!(obj instanceof ThermalBrightnessThrottlingData)) {
                 return false;
             }
 
-            BrightnessThrottlingData otherBrightnessThrottlingData = (BrightnessThrottlingData) obj;
-            return throttlingLevels.equals(otherBrightnessThrottlingData.throttlingLevels);
+            ThermalBrightnessThrottlingData otherData = (ThermalBrightnessThrottlingData) obj;
+            return throttlingLevels.equals(otherData.throttlingLevels);
         }
 
         @Override
@@ -3148,7 +3141,7 @@
         }
 
         @VisibleForTesting
-        BrightnessThrottlingData(List<ThrottlingLevel> inLevels) {
+        ThermalBrightnessThrottlingData(List<ThrottlingLevel> inLevels) {
             throttlingLevels = new ArrayList<>(inLevels.size());
             for (ThrottlingLevel level : inLevels) {
                 throttlingLevels.add(new ThrottlingLevel(level.thermalStatus, level.brightness));
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2322e03..5e3990a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -515,8 +515,8 @@
     private boolean mIsEnabled;
     private boolean mIsInTransition;
 
-    // The id of the brightness throttling policy that should be used.
-    private String mBrightnessThrottlingDataId;
+    // The id of the thermal brightness throttling policy that should be used.
+    private String mThermalBrightnessThrottlingDataId;
 
     // DPCs following the brightness of this DPC. This is used in concurrent displays mode - there
     // is one lead display, the additional displays follow the brightness value of the lead display.
@@ -556,7 +556,8 @@
         mHandler = new DisplayControllerHandler(handler.getLooper());
         mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
         mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
-        mBrightnessThrottlingDataId = logicalDisplay.getBrightnessThrottlingDataIdLocked();
+        mThermalBrightnessThrottlingDataId =
+            logicalDisplay.getThermalBrightnessThrottlingDataIdLocked();
 
         if (mDisplayId == Display.DEFAULT_DISPLAY) {
             mBatteryStats = BatteryStatsService.getService();
@@ -891,8 +892,8 @@
         final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
         final boolean isEnabled = mLogicalDisplay.isEnabledLocked();
         final boolean isInTransition = mLogicalDisplay.isInTransitionLocked();
-        final String brightnessThrottlingDataId =
-                mLogicalDisplay.getBrightnessThrottlingDataIdLocked();
+        final String thermalBrightnessThrottlingDataId =
+                mLogicalDisplay.getThermalBrightnessThrottlingDataIdLocked();
         mHandler.postAtTime(() -> {
             boolean changed = false;
             if (mDisplayDevice != device) {
@@ -901,7 +902,7 @@
                 mUniqueDisplayId = uniqueId;
                 mDisplayStatsId = mUniqueDisplayId.hashCode();
                 mDisplayDeviceConfig = config;
-                mBrightnessThrottlingDataId = brightnessThrottlingDataId;
+                mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
                 loadFromDisplayDeviceConfig(token, info, hbmMetadata);
                 loadNitBasedBrightnessSetting();
 
@@ -909,12 +910,13 @@
                 // last command that was sent to change it's state. Let's assume it is unknown so
                 // that we trigger a change immediately.
                 mPowerState.resetScreenState();
-            } else if (!mBrightnessThrottlingDataId.equals(brightnessThrottlingDataId)) {
+            } else if (
+                    !mThermalBrightnessThrottlingDataId.equals(thermalBrightnessThrottlingDataId)) {
                 changed = true;
-                mBrightnessThrottlingDataId = brightnessThrottlingDataId;
-                mBrightnessThrottler.loadBrightnessThrottlingDataFromDisplayDeviceConfig(
-                        config.getBrightnessThrottlingDataMapByThrottlingId(),
-                        mBrightnessThrottlingDataId,
+                mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
+                mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
+                        config.getThermalBrightnessThrottlingDataMapByThrottlingId(),
+                        mThermalBrightnessThrottlingDataId,
                         mUniqueDisplayId);
             }
             if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
@@ -983,9 +985,9 @@
                                 sdrBrightness, maxDesiredHdrSdrRatio);
                     }
                 });
-        mBrightnessThrottler.loadBrightnessThrottlingDataFromDisplayDeviceConfig(
-                mDisplayDeviceConfig.getBrightnessThrottlingDataMapByThrottlingId(),
-                mBrightnessThrottlingDataId, mUniqueDisplayId);
+        mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
+                mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(),
+                mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
     }
 
     private void sendUpdatePowerState() {
@@ -2121,8 +2123,8 @@
                 () -> {
                     sendUpdatePowerState();
                     postBrightnessChangeRunnable();
-                }, mUniqueDisplayId, mLogicalDisplay.getBrightnessThrottlingDataIdLocked(),
-                ddConfig.getBrightnessThrottlingDataMapByThrottlingId());
+                }, mUniqueDisplayId, mLogicalDisplay.getThermalBrightnessThrottlingDataIdLocked(),
+                ddConfig.getThermalBrightnessThrottlingDataMapByThrottlingId());
     }
 
     private void blockScreenOn() {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index d2af88b..23e606c 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -400,8 +400,8 @@
     private boolean mIsEnabled;
     private boolean mIsInTransition;
 
-    // The id of the brightness throttling policy that should be used.
-    private String mBrightnessThrottlingDataId;
+    // The id of the thermal brightness throttling policy that should be used.
+    private String mThermalBrightnessThrottlingDataId;
 
     // DPCs following the brightness of this DPC. This is used in concurrent displays mode - there
     // is one lead display, the additional displays follow the brightness value of the lead display.
@@ -439,7 +439,8 @@
         mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
         mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(context, mDisplayId);
         mTag = "DisplayPowerController2[" + mDisplayId + "]";
-        mBrightnessThrottlingDataId = logicalDisplay.getBrightnessThrottlingDataIdLocked();
+        mThermalBrightnessThrottlingDataId =
+            logicalDisplay.getThermalBrightnessThrottlingDataIdLocked();
 
         mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
         mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
@@ -707,8 +708,8 @@
         final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
         final boolean isEnabled = mLogicalDisplay.isEnabledLocked();
         final boolean isInTransition = mLogicalDisplay.isInTransitionLocked();
-        final String brightnessThrottlingDataId =
-                mLogicalDisplay.getBrightnessThrottlingDataIdLocked();
+        final String thermalBrightnessThrottlingDataId =
+                mLogicalDisplay.getThermalBrightnessThrottlingDataIdLocked();
 
         mHandler.postAtTime(() -> {
             boolean changed = false;
@@ -718,7 +719,7 @@
                 mUniqueDisplayId = uniqueId;
                 mDisplayStatsId = mUniqueDisplayId.hashCode();
                 mDisplayDeviceConfig = config;
-                mBrightnessThrottlingDataId = brightnessThrottlingDataId;
+                mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
                 loadFromDisplayDeviceConfig(token, info, hbmMetadata);
                 mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config);
 
@@ -726,12 +727,13 @@
                 // last command that was sent to change it's state. Let's assume it is unknown so
                 // that we trigger a change immediately.
                 mPowerState.resetScreenState();
-            } else if (!mBrightnessThrottlingDataId.equals(brightnessThrottlingDataId)) {
+            } else if (
+                    !mThermalBrightnessThrottlingDataId.equals(thermalBrightnessThrottlingDataId)) {
                 changed = true;
-                mBrightnessThrottlingDataId = brightnessThrottlingDataId;
-                mBrightnessThrottler.loadBrightnessThrottlingDataFromDisplayDeviceConfig(
-                        config.getBrightnessThrottlingDataMapByThrottlingId(),
-                        mBrightnessThrottlingDataId,
+                mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
+                mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
+                        config.getThermalBrightnessThrottlingDataMapByThrottlingId(),
+                        mThermalBrightnessThrottlingDataId,
                         mUniqueDisplayId);
             }
             if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
@@ -797,9 +799,9 @@
                                 sdrBrightness, maxDesiredHdrSdrRatio);
                     }
                 });
-        mBrightnessThrottler.loadBrightnessThrottlingDataFromDisplayDeviceConfig(
-                mDisplayDeviceConfig.getBrightnessThrottlingDataMapByThrottlingId(),
-                mBrightnessThrottlingDataId, mUniqueDisplayId);
+        mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
+                mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(),
+                mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
     }
 
     private void sendUpdatePowerState() {
@@ -1762,8 +1764,8 @@
                 () -> {
                     sendUpdatePowerState();
                     postBrightnessChangeRunnable();
-                }, mUniqueDisplayId, mLogicalDisplay.getBrightnessThrottlingDataIdLocked(),
-                ddConfig.getBrightnessThrottlingDataMapByThrottlingId());
+                }, mUniqueDisplayId, mLogicalDisplay.getThermalBrightnessThrottlingDataIdLocked(),
+                ddConfig.getThermalBrightnessThrottlingDataMapByThrottlingId());
     }
 
     private void blockScreenOn() {
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index ca208ac..11160a5 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -22,13 +22,8 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.IThermalEventListener;
-import android.os.IThermalService;
 import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.os.Temperature;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.MathUtils;
@@ -75,7 +70,6 @@
     private final Runnable mHbmChangeCallback;
     private final Runnable mRecalcRunnable;
     private final Clock mClock;
-    private final SkinThermalStatusObserver mSkinThermalStatusObserver;
     private final Context mContext;
     private final SettingsObserver mSettingsObserver;
     private final Injector mInjector;
@@ -100,10 +94,8 @@
 
     private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
     private boolean mIsHdrLayerPresent = false;
-
     // mMaxDesiredHdrSdrRatio should only be applied when there is a valid backlight->nits mapping
     private float mMaxDesiredHdrSdrRatio = DEFAULT_MAX_DESIRED_HDR_SDR_RATIO;
-    private boolean mIsThermalStatusWithinLimit = true;
     private boolean mIsBlockedByLowPowerMode = false;
     private int mWidth;
     private int mHeight;
@@ -138,7 +130,6 @@
         mBrightnessMax = brightnessMax;
         mHbmChangeCallback = hbmChangeCallback;
         mHighBrightnessModeMetadata = hbmMetadata;
-        mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
         mSettingsObserver = new SettingsObserver(mHandler);
         mRecalcRunnable = this::recalculateTimeAllowance;
         mHdrListener = new HdrListener();
@@ -261,7 +252,6 @@
 
     void stop() {
         registerHdrListener(null /*displayToken*/);
-        mSkinThermalStatusObserver.stopObserving();
         mSettingsObserver.stopObserving();
     }
 
@@ -278,15 +268,10 @@
         mDisplayStatsId = displayUniqueId.hashCode();
 
         unregisterHdrListener();
-        mSkinThermalStatusObserver.stopObserving();
         mSettingsObserver.stopObserving();
         if (deviceSupportsHbm()) {
             registerHdrListener(displayToken);
             recalculateTimeAllowance();
-            if (mHbmData.thermalStatusLimit > PowerManager.THERMAL_STATUS_NONE) {
-                mIsThermalStatusWithinLimit = true;
-                mSkinThermalStatusObserver.startObserving();
-            }
             if (!mHbmData.allowInLowPowerMode) {
                 mIsBlockedByLowPowerMode = false;
                 mSettingsObserver.startObserving();
@@ -327,7 +312,6 @@
         pw.println("  mIsTimeAvailable= " + mIsTimeAvailable);
         pw.println("  mRunningStartTimeMillis="
                 + TimeUtils.formatUptime(mHighBrightnessModeMetadata.getRunningStartTimeMillis()));
-        pw.println("  mIsThermalStatusWithinLimit=" + mIsThermalStatusWithinLimit);
         pw.println("  mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode);
         pw.println("  width*height=" + mWidth + "*" + mHeight);
         pw.println("  mEvents=");
@@ -344,8 +328,6 @@
             }
             lastStartTime = dumpHbmEvent(pw, event);
         }
-
-        mSkinThermalStatusObserver.dump(pw);
     }
 
     private long dumpHbmEvent(PrintWriter pw, HbmEvent event) {
@@ -367,7 +349,7 @@
         // See {@link #getHdrBrightnessValue}.
         return !mIsHdrLayerPresent
                 && (mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange
-                && mIsThermalStatusWithinLimit && !mIsBlockedByLowPowerMode);
+                && !mIsBlockedByLowPowerMode);
     }
 
     private boolean deviceSupportsHbm() {
@@ -469,7 +451,6 @@
                     + ", isAutoBrightnessEnabled: " +  mIsAutoBrightnessEnabled
                     + ", mIsTimeAvailable: " + mIsTimeAvailable
                     + ", mIsInAllowedAmbientRange: " + mIsInAllowedAmbientRange
-                    + ", mIsThermalStatusWithinLimit: " + mIsThermalStatusWithinLimit
                     + ", mIsBlockedByLowPowerMode: " + mIsBlockedByLowPowerMode
                     + ", mBrightness: " + mBrightness
                     + ", mUnthrottledBrightness: " + mUnthrottledBrightness
@@ -499,13 +480,12 @@
     }
 
     private void updateHbmStats(int newMode) {
-        final float transitionPoint = mHbmData.transitionPoint;
         int state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF;
         if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
-                && getHdrBrightnessValue() > transitionPoint) {
+                && getHdrBrightnessValue() > mHbmData.transitionPoint) {
             state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR;
         } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT
-                && mBrightness > transitionPoint) {
+                && mBrightness > mHbmData.transitionPoint) {
             state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT;
         }
         if (state == mHbmStatsState) {
@@ -519,16 +499,6 @@
         final boolean newHbmSv =
                 (state == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT);
         if (oldHbmSv && !newHbmSv) {
-            // HighBrightnessModeController (HBMC) currently supports throttling from two sources:
-            //     1. Internal, received from HBMC.SkinThermalStatusObserver.notifyThrottling()
-            //     2. External, received from HBMC.onBrightnessChanged()
-            // TODO(b/216373254): Deprecate internal throttling source
-            final boolean internalThermalThrottling = !mIsThermalStatusWithinLimit;
-            final boolean externalThermalThrottling =
-                mUnthrottledBrightness > transitionPoint && // We would've liked HBM brightness...
-                mBrightness <= transitionPoint &&           // ...but we got NBM, because of...
-                mThrottlingReason == BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; // ...thermals.
-
             // If more than one conditions are flipped and turn off HBM sunlight
             // visibility, only one condition will be reported to make it simple.
             if (!mIsAutoBrightnessEnabled && mIsAutoBrightnessOffByState) {
@@ -541,7 +511,7 @@
                 reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP;
             } else if (!mIsTimeAvailable) {
                 reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT;
-            } else if (internalThermalThrottling || externalThermalThrottling) {
+            } else if (isThermalThrottlingActive()) {
                 reason = FrameworkStatsLog
                                  .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT;
             } else if (mIsHdrLayerPresent) {
@@ -561,6 +531,14 @@
         mHbmStatsState = state;
     }
 
+    @VisibleForTesting
+    boolean isThermalThrottlingActive() {
+        // We would've liked HBM, but we got NBM (normal brightness mode) because of thermals.
+        return mUnthrottledBrightness > mHbmData.transitionPoint
+                && mBrightness <= mHbmData.transitionPoint
+                && mThrottlingReason == BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
+    }
+
     private String hbmStatsStateToString(int hbmStatsState) {
         switch (hbmStatsState) {
         case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF:
@@ -635,82 +613,6 @@
         }
     }
 
-    private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
-        private final Injector mInjector;
-        private final Handler mHandler;
-
-        private IThermalService mThermalService;
-        private boolean mStarted;
-
-        SkinThermalStatusObserver(Injector injector, Handler handler) {
-            mInjector = injector;
-            mHandler = handler;
-        }
-
-        @Override
-        public void notifyThrottling(Temperature temp) {
-            if (DEBUG) {
-                Slog.d(TAG, "New thermal throttling status "
-                        + ", current thermal status = " + temp.getStatus()
-                        + ", threshold = " + mHbmData.thermalStatusLimit);
-            }
-            mHandler.post(() -> {
-                mIsThermalStatusWithinLimit = temp.getStatus() <= mHbmData.thermalStatusLimit;
-                // This recalculates HbmMode and runs mHbmChangeCallback if the mode has changed
-                updateHbmMode();
-            });
-        }
-
-        void startObserving() {
-            if (mStarted) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Thermal status observer already started");
-                }
-                return;
-            }
-            mThermalService = mInjector.getThermalService();
-            if (mThermalService == null) {
-                Slog.w(TAG, "Could not observe thermal status. Service not available");
-                return;
-            }
-            try {
-                // We get a callback immediately upon registering so there's no need to query
-                // for the current value.
-                mThermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN);
-                mStarted = true;
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to register thermal status listener", e);
-            }
-        }
-
-        void stopObserving() {
-            mIsThermalStatusWithinLimit = true;
-            if (!mStarted) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Stop skipped because thermal status observer not started");
-                }
-                return;
-            }
-            try {
-                mThermalService.unregisterThermalEventListener(this);
-                mStarted = false;
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to unregister thermal status listener", e);
-            }
-            mThermalService = null;
-        }
-
-        void dump(PrintWriter writer) {
-            writer.println("  SkinThermalStatusObserver:");
-            writer.println("    mStarted: " + mStarted);
-            if (mThermalService != null) {
-                writer.println("    ThermalService available");
-            } else {
-                writer.println("    ThermalService not available");
-            }
-        }
-    }
-
     private final class SettingsObserver extends ContentObserver {
         private final Uri mLowPowerModeSetting = Settings.Global.getUriFor(
                 Settings.Global.LOW_POWER_MODE);
@@ -766,11 +668,6 @@
             return SystemClock::uptimeMillis;
         }
 
-        public IThermalService getThermalService() {
-            return IThermalService.Stub.asInterface(
-                    ServiceManager.getService(Context.THERMAL_SERVICE));
-        }
-
         public void reportHbmStateChange(int display, int state, int reason) {
             FrameworkStatsLog.write(
                     FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED, display, state, reason);
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index dee4cde..dab00d8 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -175,11 +175,11 @@
     private boolean mDirty = false;
 
     /**
-     * The ID of the brightness throttling data that should be used. This can change e.g. in
-     * concurrent displays mode in which a stricter brightness throttling policy might need to be
-     * used.
+     * The ID of the thermal brightness throttling data that should be used. This can change e.g.
+     * in concurrent displays mode in which a stricter brightness throttling policy might need to
+     * be used.
      */
-    private String mBrightnessThrottlingDataId;
+    private String mThermalBrightnessThrottlingDataId;
 
     public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
         mDisplayId = displayId;
@@ -189,7 +189,7 @@
         mTempFrameRateOverride = new SparseArray<>();
         mIsEnabled = true;
         mIsInTransition = false;
-        mBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
+        mThermalBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
     }
 
     public void setDevicePositionLocked(int position) {
@@ -349,7 +349,7 @@
      *
      * @param refreshRanges new refreshRateThermalThrottling ranges limited by layout or default
      */
-    public void updateRefreshRateThermalThrottling(
+    public void updateThermalRefreshRateThrottling(
             @Nullable SparseArray<SurfaceControl.RefreshRateRange> refreshRanges) {
         if (refreshRanges == null) {
             refreshRanges = new SparseArray<>();
@@ -872,16 +872,16 @@
     /**
      * @return The ID of the brightness throttling data that this display should use.
      */
-    public String getBrightnessThrottlingDataIdLocked() {
-        return mBrightnessThrottlingDataId;
+    public String getThermalBrightnessThrottlingDataIdLocked() {
+        return mThermalBrightnessThrottlingDataId;
     }
 
     /**
      * @param brightnessThrottlingDataId The ID of the brightness throttling data that this
      *                                  display should use.
      */
-    public void setBrightnessThrottlingDataIdLocked(String brightnessThrottlingDataId) {
-        mBrightnessThrottlingDataId =
+    public void setThermalBrightnessThrottlingDataIdLocked(String brightnessThrottlingDataId) {
+        mThermalBrightnessThrottlingDataId =
                 brightnessThrottlingDataId;
     }
 
@@ -950,7 +950,7 @@
         pw.println("mFrameRateOverrides=" + Arrays.toString(mFrameRateOverrides));
         pw.println("mPendingFrameRateOverrideUids=" + mPendingFrameRateOverrideUids);
         pw.println("mDisplayGroupName=" + mDisplayGroupName);
-        pw.println("mBrightnessThrottlingDataId=" + mBrightnessThrottlingDataId);
+        pw.println("mThermalBrightnessThrottlingDataId=" + mThermalBrightnessThrottlingDataId);
         pw.println("mLeadDisplayId=" + mLeadDisplayId);
     }
 
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 424eedc..254441c2 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -1022,17 +1022,17 @@
             newDisplay.updateLayoutLimitedRefreshRateLocked(
                     config.getRefreshRange(displayLayout.getRefreshRateZoneId())
             );
-            newDisplay.updateRefreshRateThermalThrottling(
-                    config.getRefreshRateThrottlingData(
+            newDisplay.updateThermalRefreshRateThrottling(
+                    config.getThermalRefreshRateThrottlingData(
                             displayLayout.getRefreshRateThermalThrottlingMapId()
                     )
             );
 
             setEnabledLocked(newDisplay, displayLayout.isEnabled());
-            newDisplay.setBrightnessThrottlingDataIdLocked(
-                    displayLayout.getBrightnessThrottlingMapId() == null
+            newDisplay.setThermalBrightnessThrottlingDataIdLocked(
+                    displayLayout.getThermalBrightnessThrottlingMapId() == null
                             ? DisplayDeviceConfig.DEFAULT_ID
-                            : displayLayout.getBrightnessThrottlingMapId());
+                            : displayLayout.getThermalBrightnessThrottlingMapId());
 
             newDisplay.setDisplayGroupNameLocked(displayLayout.getDisplayGroupName());
         }
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 634f31d..b55d7d5 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -234,11 +234,11 @@
         // {@link DeviceStateToLayoutMap.POSITION_UNKNOWN} is unspecified.
         private final int mPosition;
 
-        // The ID of the brightness throttling map that should be used. This can change e.g. in
-        // concurrent displays mode in which a stricter brightness throttling policy might need to
-        // be used.
+        // The ID of the thermal brightness throttling map that should be used. This can change
+        // e.g. in concurrent displays mode in which a stricter brightness throttling policy might
+        // need to be used.
         @Nullable
-        private final String mBrightnessThrottlingMapId;
+        private final String mThermalBrightnessThrottlingMapId;
 
         // The ID of the lead display that this display will follow in a layout. -1 means no lead.
         private final int mLeadDisplayId;
@@ -248,7 +248,7 @@
         private final String mRefreshRateZoneId;
 
         @Nullable
-        private final String mRefreshRateThermalThrottlingMapId;
+        private final String mThermalRefreshRateThrottlingMapId;
 
         private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,
                 @NonNull String displayGroupName, String brightnessThrottlingMapId, int position,
@@ -259,9 +259,9 @@
             mIsEnabled = isEnabled;
             mDisplayGroupName = displayGroupName;
             mPosition = position;
-            mBrightnessThrottlingMapId = brightnessThrottlingMapId;
+            mThermalBrightnessThrottlingMapId = brightnessThrottlingMapId;
             mRefreshRateZoneId = refreshRateZoneId;
-            mRefreshRateThermalThrottlingMapId = refreshRateThermalThrottlingMapId;
+            mThermalRefreshRateThrottlingMapId = refreshRateThermalThrottlingMapId;
             mLeadDisplayId = leadDisplayId;
         }
 
@@ -273,10 +273,10 @@
                     + ", displayGroupName: " + mDisplayGroupName
                     + ", addr: " + mAddress
                     +  ((mPosition == POSITION_UNKNOWN) ? "" : ", position: " + mPosition)
-                    + ", brightnessThrottlingMapId: " + mBrightnessThrottlingMapId
+                    + ", mThermalBrightnessThrottlingMapId: " + mThermalBrightnessThrottlingMapId
                     + ", mRefreshRateZoneId: " + mRefreshRateZoneId
                     + ", mLeadDisplayId: " + mLeadDisplayId
-                    + ", mRefreshRateThermalThrottlingMapId: " + mRefreshRateThermalThrottlingMapId
+                    + ", mThermalRefreshRateThrottlingMapId: " + mThermalRefreshRateThrottlingMapId
                     + "}";
         }
 
@@ -293,12 +293,12 @@
                     && otherDisplay.mLogicalDisplayId == this.mLogicalDisplayId
                     && this.mDisplayGroupName.equals(otherDisplay.mDisplayGroupName)
                     && this.mAddress.equals(otherDisplay.mAddress)
-                    && Objects.equals(mBrightnessThrottlingMapId,
-                    otherDisplay.mBrightnessThrottlingMapId)
+                    && Objects.equals(mThermalBrightnessThrottlingMapId,
+                    otherDisplay.mThermalBrightnessThrottlingMapId)
                     && Objects.equals(otherDisplay.mRefreshRateZoneId, this.mRefreshRateZoneId)
                     && this.mLeadDisplayId == otherDisplay.mLeadDisplayId
-                    && Objects.equals(mRefreshRateThermalThrottlingMapId,
-                    otherDisplay.mRefreshRateThermalThrottlingMapId);
+                    && Objects.equals(mThermalRefreshRateThrottlingMapId,
+                    otherDisplay.mThermalRefreshRateThrottlingMapId);
         }
 
         @Override
@@ -309,10 +309,10 @@
             result = 31 * result + mLogicalDisplayId;
             result = 31 * result + mDisplayGroupName.hashCode();
             result = 31 * result + mAddress.hashCode();
-            result = 31 * result + mBrightnessThrottlingMapId.hashCode();
+            result = 31 * result + mThermalBrightnessThrottlingMapId.hashCode();
             result = 31 * result + Objects.hashCode(mRefreshRateZoneId);
             result = 31 * result + mLeadDisplayId;
-            result = 31 * result + Objects.hashCode(mRefreshRateThermalThrottlingMapId);
+            result = 31 * result + Objects.hashCode(mThermalRefreshRateThrottlingMapId);
             return result;
         }
 
@@ -338,12 +338,12 @@
         }
 
         /**
-         * Gets the id of the brightness throttling map that should be used.
-         * @return The ID of the brightness throttling map that this display should use, null if
-         *         unspecified, will fall back to default.
+         * Gets the id of the thermal brightness throttling map that should be used.
+         * @return The ID of the thermal brightness throttling map that this display should use,
+         *         null if unspecified, will fall back to default.
          */
-        public String getBrightnessThrottlingMapId() {
-            return mBrightnessThrottlingMapId;
+        public String getThermalBrightnessThrottlingMapId() {
+            return mThermalBrightnessThrottlingMapId;
         }
 
         /**
@@ -361,7 +361,7 @@
         }
 
         public String getRefreshRateThermalThrottlingMapId() {
-            return mRefreshRateThermalThrottlingMapId;
+            return mThermalRefreshRateThrottlingMapId;
         }
     }
 }
diff --git a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
index f93d9ee..c04735d 100644
--- a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
+++ b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
@@ -102,7 +102,7 @@
     //region DisplayManager.DisplayListener
     @Override
     public void onDisplayAdded(int displayId) {
-        updateRefreshRateThermalThrottling(displayId);
+        updateThermalRefreshRateThrottling(displayId);
         if (mLoggingEnabled) {
             Slog.d(TAG, "Display added:" + displayId);
         }
@@ -122,7 +122,7 @@
 
     @Override
     public void onDisplayChanged(int displayId) {
-        updateRefreshRateThermalThrottling(displayId);
+        updateThermalRefreshRateThrottling(displayId);
         if (mLoggingEnabled) {
             Slog.d(TAG, "Display changed:" + displayId);
         }
@@ -150,7 +150,7 @@
         }
     }
 
-    private void updateRefreshRateThermalThrottling(int displayId) {
+    private void updateThermalRefreshRateThrottling(int displayId) {
         DisplayInfo displayInfo = new DisplayInfo();
         mInjector.getDisplayInfo(displayId, displayInfo);
         SparseArray<SurfaceControl.RefreshRateRange> throttlingMap =
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
index c05a03e..c76ca2b 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
@@ -286,7 +286,6 @@
                     final InputMethodSubtype.InputMethodSubtypeBuilder
                             builder = new InputMethodSubtype.InputMethodSubtypeBuilder()
                             .setSubtypeNameResId(label)
-                            .setSubtypeNameOverride(untranslatableName)
                             .setPhysicalKeyboardHint(
                                     pkLanguageTag == null ? null : new ULocale(pkLanguageTag),
                                     pkLayoutType == null ? "" : pkLayoutType)
@@ -302,6 +301,9 @@
                     if (subtypeId != InputMethodSubtype.SUBTYPE_ID_NONE) {
                         builder.setSubtypeId(subtypeId);
                     }
+                    if (untranslatableName != null) {
+                        builder.setSubtypeNameOverride(untranslatableName);
+                    }
                     tempSubtypesArray.add(builder.build());
                 }
             }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index c212e8e..44ae454 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -19,7 +19,6 @@
 import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
 import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
 
-import android.annotation.Nullable;
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -66,8 +65,8 @@
     private boolean mShowImeWithHardKeyboard;
 
     @GuardedBy("ImfLock.class")
-    @Nullable
-    private InputMethodDialogWindowContext mDialogWindowContext;
+    private final InputMethodDialogWindowContext mDialogWindowContext =
+            new InputMethodDialogWindowContext();
 
     InputMethodMenuController(InputMethodManagerService service) {
         mService = service;
@@ -125,13 +124,11 @@
                 }
             }
 
-            if (mDialogWindowContext == null) {
-                mDialogWindowContext = new InputMethodDialogWindowContext();
-            }
             final Context dialogWindowContext = mDialogWindowContext.get(displayId);
             mDialogBuilder = new AlertDialog.Builder(dialogWindowContext);
             mDialogBuilder.setOnCancelListener(dialog -> hideInputMethodMenu());
 
+            // TODO(b/277061090): refactor UI components should not be created while holding a lock.
             final Context dialogContext = mDialogBuilder.getContext();
             final TypedArray a = dialogContext.obtainStyledAttributes(null,
                     com.android.internal.R.styleable.DialogPreference,
@@ -199,10 +196,11 @@
             attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
             attrs.setTitle("Select input method");
             w.setAttributes(attrs);
+            // TODO(b/277062834) decouple/remove dependency on IMMS
             mService.updateSystemUiLocked();
             mService.sendOnNavButtonFlagsChangedLocked();
-            mSwitchingDialog.show();
         }
+        mSwitchingDialog.show();
     }
 
     private boolean isScreenLocked() {
@@ -276,6 +274,7 @@
         private final int mTextViewResourceId;
         private final List<ImeSubtypeListItem> mItemsList;
         public int mCheckedItem;
+
         private ImeSubtypeListAdapter(Context context, int textViewResourceId,
                 List<ImeSubtypeListItem> itemsList, int checkedItem) {
             super(context, textViewResourceId, itemsList);
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 43e346a..2d40661 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -323,7 +323,7 @@
      */
     void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId,
             LocaleList locales) {
-        String installingPackageName = getInstallingPackageName(appPackageName);
+        String installingPackageName = getInstallingPackageName(appPackageName, userId);
         if (installingPackageName != null) {
             Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED,
                     appPackageName, locales);
@@ -464,7 +464,7 @@
      * Checks if the calling app is the installer of the app whose locale changed.
      */
     private boolean isCallerInstaller(String appPackageName, int userId) {
-        String installingPackageName = getInstallingPackageName(appPackageName);
+        String installingPackageName = getInstallingPackageName(appPackageName, userId);
         if (installingPackageName != null) {
             // Get the uid of installer-on-record to compare with the calling uid.
             int installerUid = getPackageUid(installingPackageName, userId);
@@ -513,10 +513,11 @@
     }
 
     @Nullable
-    String getInstallingPackageName(String packageName) {
+    String getInstallingPackageName(String packageName, int userId) {
         try {
-            return mContext.getPackageManager()
-                    .getInstallSourceInfo(packageName).getInstallingPackageName();
+            return mContext.createContextAsUser(UserHandle.of(userId), /* flags= */
+                    0).getPackageManager().getInstallSourceInfo(
+                    packageName).getInstallingPackageName();
         } catch (PackageManager.NameNotFoundException e) {
             Slog.w(TAG, "Package not found " + packageName);
         }
diff --git a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
index 215c653..373d355 100644
--- a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
+++ b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
@@ -152,9 +152,10 @@
     void onPackageUpdateFinished(String packageName, int uid) {
         try {
             if ((!mUpdatedApps.contains(packageName)) && isUpdatedSystemApp(packageName)) {
+                int userId = UserHandle.getUserId(uid);
                 // If a system app is updated, verify that it has an installer-on-record.
                 String installingPackageName = mLocaleManagerService.getInstallingPackageName(
-                        packageName);
+                        packageName, userId);
                 if (installingPackageName == null) {
                     // We want to broadcast the locales info to the installer.
                     // If this app does not have an installer then do nothing.
@@ -162,7 +163,6 @@
                 }
 
                 try {
-                    int userId = UserHandle.getUserId(uid);
                     // Fetch the app-specific locales.
                     // If non-empty then send the info to the installer.
                     LocaleList appLocales = mLocaleManagerService.getApplicationLocales(
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 6264851..f3b743c9 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1516,11 +1516,6 @@
                 && !getSeparateProfileChallengeEnabledInternal(userId);
     }
 
-    private boolean isProfileWithSeparatedLock(int userId) {
-        return isCredentialSharableWithParent(userId)
-                && getSeparateProfileChallengeEnabledInternal(userId);
-    }
-
     /**
      * Send credentials for user {@code userId} to {@link RecoverableKeyStoreManager} during an
      * unlock operation.
@@ -2773,9 +2768,19 @@
 
         activateEscrowTokens(sp, userId);
 
-        if (isProfileWithSeparatedLock(userId)) {
-            setDeviceUnlockedForUser(userId);
+        if (isCredentialSharableWithParent(userId)) {
+            if (getSeparateProfileChallengeEnabledInternal(userId)) {
+                setDeviceUnlockedForUser(userId);
+            } else {
+                // Here only clear StrongAuthFlags for a profile that has a unified challenge.
+                // StrongAuth for a profile with a separate challenge is handled differently and
+                // is cleared after the user successfully confirms the separate challenge to enter
+                // the profile. StrongAuth for the full user (e.g. userId 0) is also handled
+                // separately by Keyguard.
+                mStrongAuth.reportUnlock(userId);
+            }
         }
+
         mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
 
         onSyntheticPasswordUnlocked(userId, sp);
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index f0e8ede..94d5aab 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -357,12 +357,16 @@
         } catch (NameNotFoundException e) {
             throw new IllegalArgumentException("No package matching :" + packageName);
         }
-
-        projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion,
-                ai.isPrivilegedApp());
-        if (isPermanentGrant) {
-            mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
-                    projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
+        final long callingToken = Binder.clearCallingIdentity();
+        try {
+            projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion,
+                    ai.isPrivilegedApp());
+            if (isPermanentGrant) {
+                mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
+                        projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
         }
         return projection;
     }
@@ -418,16 +422,9 @@
             if (packageName == null || packageName.isEmpty()) {
                 throw new IllegalArgumentException("package name must not be empty");
             }
-            MediaProjection projection;
             final UserHandle callingUser = Binder.getCallingUserHandle();
-            final long callingToken = Binder.clearCallingIdentity();
-            try {
-                projection = createProjectionInternal(uid, packageName, type, isPermanentGrant,
-                        callingUser, false);
-            } finally {
-                Binder.restoreCallingIdentity(callingToken);
-            }
-            return projection;
+            return createProjectionInternal(uid, packageName, type, isPermanentGrant,
+                    callingUser, false);
         }
 
         @Override // Binder call
diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java
index d3dea0d..b8900d7 100644
--- a/services/core/java/com/android/server/notification/BubbleExtractor.java
+++ b/services/core/java/com/android/server/notification/BubbleExtractor.java
@@ -16,7 +16,6 @@
 package com.android.server.notification;
 
 import static android.app.Notification.FLAG_BUBBLE;
-import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
 import static android.app.NotificationChannel.ALLOW_BUBBLE_OFF;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
@@ -81,7 +80,7 @@
                 && !mActivityManager.isLowRamDevice()
                 && record.isConversation()
                 && record.getShortcutInfo() != null
-                && (record.getNotification().flags & FLAG_FOREGROUND_SERVICE) == 0;
+                && !record.getNotification().isFgsOrUij();
 
         boolean userEnabledBubbles = mConfig.bubblesEnabled(record.getUser());
         int appPreference =
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 6f0903c..446c4f7 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -165,7 +165,7 @@
         if (isCallStyle(record)) {
             return true;
         }
-        if (!isOngoing(record)) {
+        if (!record.getNotification().isFgsOrUij()) {
             return false;
         }
         return isCallCategory(record) || isMediaNotification(record);
@@ -199,11 +199,6 @@
         return false;
     }
 
-    private boolean isOngoing(NotificationRecord record) {
-        final int ongoingFlags = Notification.FLAG_FOREGROUND_SERVICE;
-        return (record.getNotification().flags & ongoingFlags) != 0;
-    }
-
     private boolean isMediaNotification(NotificationRecord record) {
         return record.getNotification().isMediaNotification();
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index bc38856..919fc71 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -35,6 +35,8 @@
 
     void removeForegroundServiceFlagFromNotification(String pkg, int notificationId, int userId);
 
+    void removeUserInitiatedJobFlagFromNotification(String pkg, int notificationId, int userId);
+
     void onConversationRemoved(String pkg, int uid, Set<String> shortcuts);
 
     /** Get the number of notification channels for a given package */
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 236ea23..75ab5b1 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -21,6 +21,7 @@
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
 import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
+import static android.app.Notification.FLAG_AUTO_CANCEL;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
 import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
@@ -29,6 +30,7 @@
 import static android.app.Notification.FLAG_NO_DISMISS;
 import static android.app.Notification.FLAG_ONGOING_EVENT;
 import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
+import static android.app.Notification.FLAG_USER_INITIATED_JOB;
 import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
 import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
 import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
@@ -163,6 +165,7 @@
 import android.app.NotificationManager.Policy;
 import android.app.PendingIntent;
 import android.app.RemoteServiceException.BadForegroundServiceNotificationException;
+import android.app.RemoteServiceException.BadUserInitiatedJobNotificationException;
 import android.app.StatsManager;
 import android.app.StatusBarManager;
 import android.app.UriGrantsManager;
@@ -304,6 +307,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
+import com.android.server.job.JobSchedulerInternal;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
 import com.android.server.notification.ManagedServices.ManagedServiceInfo;
@@ -1156,8 +1160,8 @@
                 StatusBarNotification sbn = r.getSbn();
                 cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(),
                         sbn.getId(), Notification.FLAG_AUTO_CANCEL,
-                        FLAG_FOREGROUND_SERVICE | FLAG_BUBBLE, false, r.getUserId(),
-                        REASON_CLICK, nv.rank, nv.count, null);
+                        FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB | FLAG_BUBBLE,
+                        false, r.getUserId(), REASON_CLICK, nv.rank, nv.count, null);
                 nv.recycle();
                 reportUserInteraction(r);
                 mAssistants.notifyAssistantNotificationClicked(r);
@@ -1265,21 +1269,26 @@
         public void onNotificationError(int callingUid, int callingPid, String pkg, String tag,
                 int id, int uid, int initialPid, String message, int userId) {
             final boolean fgService;
+            final boolean uiJob;
             synchronized (mNotificationLock) {
                 NotificationRecord r = findNotificationLocked(pkg, tag, id, userId);
                 fgService = r != null && (r.getNotification().flags & FLAG_FOREGROUND_SERVICE) != 0;
+                uiJob = r != null && (r.getNotification().flags & FLAG_USER_INITIATED_JOB) != 0;
             }
             cancelNotification(callingUid, callingPid, pkg, tag, id, 0, 0, false, userId,
                     REASON_ERROR, null);
-            if (fgService) {
-                // Still crash for foreground services, preventing the not-crash behaviour abused
-                // by apps to give us a garbage notification and silently start a fg service.
+            if (fgService || uiJob) {
+                // Still crash for foreground services or user-initiated jobs, preventing the
+                // not-crash behaviour abused by apps to give us a garbage notification and
+                // silently start a fg service or user-initiated job.
+                final int exceptionTypeId = fgService
+                        ? BadForegroundServiceNotificationException.TYPE_ID
+                        : BadUserInitiatedJobNotificationException.TYPE_ID;
                 Binder.withCleanCallingIdentity(
                         () -> mAm.crashApplicationWithType(uid, initialPid, pkg, -1,
                             "Bad notification(tag=" + tag + ", id=" + id + ") posted from package "
                                 + pkg + ", crashing app(uid=" + uid + ", pid=" + initialPid + "): "
-                                + message, true /* force */,
-                                BadForegroundServiceNotificationException.TYPE_ID));
+                                + message, true /* force */, exceptionTypeId));
             }
         }
 
@@ -1689,8 +1698,8 @@
                     cancelNotification(record.getSbn().getUid(), record.getSbn().getInitialPid(),
                             record.getSbn().getPackageName(), record.getSbn().getTag(),
                             record.getSbn().getId(), 0,
-                            FLAG_FOREGROUND_SERVICE, true, record.getUserId(),
-                            REASON_TIMEOUT, null);
+                            FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB,
+                            true, record.getUserId(), REASON_TIMEOUT, null);
                 }
             }
         }
@@ -3519,10 +3528,10 @@
             userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                     Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg);
 
-            // Don't allow the app to cancel active FGS notifications
+            // Don't allow the app to cancel active FGS or UIJ notifications
             cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(),
-                    pkg, null, 0, FLAG_FOREGROUND_SERVICE, true, userId,
-                    REASON_APP_CANCEL_ALL, null);
+                    pkg, null, 0, FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB,
+                    true, userId, REASON_APP_CANCEL_ALL, null);
         }
 
         @Override
@@ -3806,6 +3815,28 @@
         }
 
         @Override
+        public boolean canUseFullScreenIntent(@NonNull AttributionSource attributionSource) {
+            final String packageName = attributionSource.getPackageName();
+            final int uid = attributionSource.getUid();
+            final int userId = UserHandle.getUserId(uid);
+            checkCallerIsSameApp(packageName, uid, userId);
+
+            final ApplicationInfo applicationInfo;
+            try {
+                applicationInfo = mPackageManagerClient.getApplicationInfoAsUser(
+                        packageName, PackageManager.MATCH_DIRECT_BOOT_AUTO, userId);
+            } catch (NameNotFoundException e) {
+                Slog.e(TAG, "Failed to getApplicationInfo() in canUseFullScreenIntent()", e);
+                return false;
+            }
+            final boolean showStickyHunIfDenied = mFlagResolver.isEnabled(
+                    SystemUiSystemPropertiesFlags.NotificationFlags
+                            .SHOW_STICKY_HUN_FOR_DENIED_FSI);
+            return checkUseFullScreenIntentPermission(attributionSource, applicationInfo,
+                    showStickyHunIfDenied /* isAppOpPermission */, false /* forDataDelivery */);
+        }
+
+        @Override
         public void updateNotificationChannelGroupForPackage(String pkg, int uid,
                 NotificationChannelGroup group) throws RemoteException {
             enforceSystemOrSystemUI("Caller not system or systemui");
@@ -3964,6 +3995,21 @@
             }
         }
 
+        // Throws a security exception if the given channel has a notification associated
+        // with an active user-initiated job.
+        private void enforceDeletingChannelHasNoUserInitiatedJob(String pkg, int userId,
+                String channelId) {
+            final JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
+            if (js != null && js.isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
+                    channelId, userId, pkg)) {
+                Slog.w(TAG, "Package u" + userId + "/" + pkg
+                        + " may not delete notification channel '"
+                        + channelId + "' with user-initiated job");
+                throw new SecurityException("Not allowed to delete channel " + channelId
+                        + " with a user-initiated job");
+            }
+        }
+
         @Override
         public void deleteNotificationChannel(String pkg, String channelId) {
             checkCallerIsSystemOrSameApp(pkg);
@@ -3973,6 +4019,7 @@
                 throw new IllegalArgumentException("Cannot delete default channel");
             }
             enforceDeletingChannelHasNoFgService(pkg, callingUser, channelId);
+            enforceDeletingChannelHasNoUserInitiatedJob(pkg, callingUser, channelId);
             cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
                     callingUser, REASON_CHANNEL_REMOVED, null);
             boolean previouslyExisted = mPreferencesHelper.deleteNotificationChannel(
@@ -4017,8 +4064,9 @@
                 final int userId = UserHandle.getUserId(callingUid);
                 List<NotificationChannel> groupChannels = groupToDelete.getChannels();
                 for (int i = 0; i < groupChannels.size(); i++) {
-                    enforceDeletingChannelHasNoFgService(pkg, userId,
-                            groupChannels.get(i).getId());
+                    final String channelId = groupChannels.get(i).getId();
+                    enforceDeletingChannelHasNoFgService(pkg, userId, channelId);
+                    enforceDeletingChannelHasNoUserInitiatedJob(pkg, userId, channelId);
                 }
                 List<NotificationChannel> deletedChannels =
                         mPreferencesHelper.deleteNotificationChannelGroup(pkg, callingUid, groupId);
@@ -6357,61 +6405,73 @@
             checkCallerIsSystem();
             mHandler.post(() -> {
                 synchronized (mNotificationLock) {
-                    int count = getNotificationCount(pkg, userId);
-                    boolean removeFgsNotification = false;
-                    if (count > MAX_PACKAGE_NOTIFICATIONS) {
-                        mUsageStats.registerOverCountQuota(pkg);
-                        removeFgsNotification = true;
-                    }
-                    if (removeFgsNotification) {
-                        NotificationRecord r = findNotificationLocked(pkg, null, notificationId,
-                                userId);
-                        if (r != null) {
-                            if (DBG) {
-                                Slog.d(TAG, "Remove FGS flag not allow. Cancel FGS notification");
-                            }
-                            removeFromNotificationListsLocked(r);
-                            cancelNotificationLocked(r, false, REASON_APP_CANCEL, true,
-                                    null, SystemClock.elapsedRealtime());
-                        }
-                    } else {
-                        // strip flag from all enqueued notifications. listeners will be informed
-                        // in post runnable.
-                        List<NotificationRecord> enqueued = findNotificationsByListLocked(
-                                mEnqueuedNotifications, pkg, null, notificationId, userId);
-                        for (int i = 0; i < enqueued.size(); i++) {
-                            removeForegroundServiceFlagLocked(enqueued.get(i));
-                        }
-
-                        // if posted notification exists, strip its flag and tell listeners
-                        NotificationRecord r = findNotificationByListLocked(
-                                mNotificationList, pkg, null, notificationId, userId);
-                        if (r != null) {
-                            removeForegroundServiceFlagLocked(r);
-                            mRankingHelper.sort(mNotificationList);
-                            mListeners.notifyPostedLocked(r, r);
-                        }
-                    }
+                    removeFlagFromNotificationLocked(pkg, notificationId, userId,
+                            FLAG_FOREGROUND_SERVICE);
                 }
             });
         }
 
         @Override
-        public void onConversationRemoved(String pkg, int uid, Set<String> shortcuts) {
-            onConversationRemovedInternal(pkg, uid, shortcuts);
+        public void removeUserInitiatedJobFlagFromNotification(String pkg, int notificationId,
+                int userId) {
+            checkCallerIsSystem();
+            mHandler.post(() -> {
+                synchronized (mNotificationLock) {
+                    removeFlagFromNotificationLocked(pkg, notificationId, userId,
+                            FLAG_USER_INITIATED_JOB);
+                }
+            });
         }
 
         @GuardedBy("mNotificationLock")
-        private void removeForegroundServiceFlagLocked(NotificationRecord r) {
-            if (r == null) {
-                return;
+        private void removeFlagFromNotificationLocked(String pkg, int notificationId, int userId,
+                int flag) {
+            int count = getNotificationCount(pkg, userId);
+            boolean removeFlagFromNotification = false;
+            if (count > MAX_PACKAGE_NOTIFICATIONS) {
+                mUsageStats.registerOverCountQuota(pkg);
+                removeFlagFromNotification = true;
             }
-            StatusBarNotification sbn = r.getSbn();
-            // NoMan adds flags FLAG_ONGOING_EVENT when it sees
-            // FLAG_FOREGROUND_SERVICE. Hence it's not enough to remove
-            // FLAG_FOREGROUND_SERVICE, we have to revert to the flags we received
-            // initially *and* force remove FLAG_FOREGROUND_SERVICE.
-            sbn.getNotification().flags = (r.mOriginalFlags & ~FLAG_FOREGROUND_SERVICE);
+            if (removeFlagFromNotification) {
+                NotificationRecord r = findNotificationLocked(pkg, null, notificationId, userId);
+                if (r != null) {
+                    if (DBG) {
+                        final String type = (flag ==  FLAG_FOREGROUND_SERVICE) ? "FGS" : "UIJ";
+                        Slog.d(TAG, "Remove " + type + " flag not allow. "
+                                + "Cancel " + type + " notification");
+                    }
+                    removeFromNotificationListsLocked(r);
+                    cancelNotificationLocked(r, false, REASON_APP_CANCEL, true,
+                            null, SystemClock.elapsedRealtime());
+                }
+            } else {
+                List<NotificationRecord> enqueued = findNotificationsByListLocked(
+                        mEnqueuedNotifications, pkg, null, notificationId, userId);
+                for (int i = 0; i < enqueued.size(); i++) {
+                    final NotificationRecord r = enqueued.get(i);
+                    if (r != null) {
+                        // strip flag from all enqueued notifications. listeners will be informed
+                        // in post runnable.
+                        StatusBarNotification sbn = r.getSbn();
+                        sbn.getNotification().flags = (r.mOriginalFlags & ~flag);
+                    }
+                }
+
+                NotificationRecord r = findNotificationByListLocked(
+                        mNotificationList, pkg, null, notificationId, userId);
+                if (r != null) {
+                    // if posted notification exists, strip its flag and tell listeners
+                    StatusBarNotification sbn = r.getSbn();
+                    sbn.getNotification().flags = (r.mOriginalFlags & ~flag);
+                    mRankingHelper.sort(mNotificationList);
+                    mListeners.notifyPostedLocked(r, r);
+                }
+            }
+        }
+
+        @Override
+        public void onConversationRemoved(String pkg, int uid, Set<String> shortcuts) {
+            onConversationRemovedInternal(pkg, uid, shortcuts);
         }
 
         @Override
@@ -6487,10 +6547,10 @@
             }
         }
 
-        // Don't allow client applications to cancel foreground service notifs or autobundled
-        // summaries.
+        // Don't allow client applications to cancel foreground service notifs, user-initiated job
+        // notifs or autobundled summaries.
         final int mustNotHaveFlags = isCallingUidSystem() ? 0 :
-                (FLAG_FOREGROUND_SERVICE | FLAG_AUTOGROUP_SUMMARY);
+                (FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB | FLAG_AUTOGROUP_SUMMARY);
         cancelNotification(uid, callingPid, pkg, tag, id, 0,
                 mustNotHaveFlags, false, userId, REASON_APP_CANCEL, null);
     }
@@ -6544,9 +6604,16 @@
         final ServiceNotificationPolicy policy = mAmi.applyForegroundServiceNotification(
                 notification, tag, id, pkg, userId);
 
+        boolean stripUijFlag = true;
+        final JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
+        if (js != null) {
+            stripUijFlag = !js.isNotificationAssociatedWithAnyUserInitiatedJobs(id, userId, pkg);
+        }
+
         // Fix the notification as best we can.
         try {
-            fixNotification(notification, pkg, tag, id, userId, notificationUid, policy);
+            fixNotification(notification, pkg, tag, id, userId, notificationUid,
+                    policy, stripUijFlag);
         } catch (Exception e) {
             if (notification.isForegroundService()) {
                 throw new SecurityException("Invalid FGS notification", e);
@@ -6555,7 +6622,6 @@
             return;
         }
 
-
         if (policy == ServiceNotificationPolicy.UPDATE_ONLY) {
             // Proceed if the notification is already showing/known, otherwise ignore
             // because the service lifecycle logic has retained responsibility for its
@@ -6614,26 +6680,25 @@
         boolean isImportanceFixed = mPermissionHelper.isPermissionFixed(pkg, userId);
         r.setImportanceFixed(isImportanceFixed);
 
-        if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
-            final boolean fgServiceShown = channel.isFgServiceShown();
+        if (notification.isFgsOrUij()) {
             if (((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
-                        || !fgServiceShown)
+                        || !channel.isUserVisibleTaskShown())
                     && (r.getImportance() == IMPORTANCE_MIN
                             || r.getImportance() == IMPORTANCE_NONE)) {
-                // Increase the importance of foreground service notifications unless the user had
-                // an opinion otherwise (and the channel hasn't yet shown a fg service).
+                // Increase the importance of fgs/uij notifications unless the user had
+                // an opinion otherwise (and the channel hasn't yet shown a fgs/uij).
                 channel.setImportance(IMPORTANCE_LOW);
                 r.setSystemImportance(IMPORTANCE_LOW);
-                if (!fgServiceShown) {
+                if (!channel.isUserVisibleTaskShown()) {
                     channel.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
-                    channel.setFgServiceShown(true);
+                    channel.setUserVisibleTaskShown(true);
                 }
                 mPreferencesHelper.updateNotificationChannel(
                         pkg, notificationUid, channel, false);
                 r.updateNotificationChannel(channel);
-            } else if (!fgServiceShown && !TextUtils.isEmpty(channelId)
+            } else if (!channel.isUserVisibleTaskShown() && !TextUtils.isEmpty(channelId)
                     && !NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
-                channel.setFgServiceShown(true);
+                channel.setUserVisibleTaskShown(true);
                 r.updateNotificationChannel(channel);
             }
         }
@@ -6726,7 +6791,8 @@
 
     @VisibleForTesting
     protected void fixNotification(Notification notification, String pkg, String tag, int id,
-            @UserIdInt int userId, int notificationUid, ServiceNotificationPolicy fgsPolicy)
+            @UserIdInt int userId, int notificationUid,
+            ServiceNotificationPolicy fgsPolicy, boolean stripUijFlag)
             throws NameNotFoundException, RemoteException {
         final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
                 pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
@@ -6736,6 +6802,14 @@
         if (notification.isForegroundService() && fgsPolicy == NOT_FOREGROUND_SERVICE) {
             notification.flags &= ~FLAG_FOREGROUND_SERVICE;
         }
+        if (notification.isUserInitiatedJob() && stripUijFlag) {
+            notification.flags &= ~FLAG_USER_INITIATED_JOB;
+        }
+
+        // Remove FLAG_AUTO_CANCEL from notifications that are associated with a FGS or UIJ.
+        if (notification.isFgsOrUij()) {
+            notification.flags &= ~FLAG_AUTO_CANCEL;
+        }
 
         // Only notifications that can be non-dismissible can have the flag FLAG_NO_DISMISS
         if (mFlagResolver.isEnabled(ALLOW_DISMISS_ONGOING)) {
@@ -6771,36 +6845,28 @@
 
         notification.flags &= ~FLAG_FSI_REQUESTED_BUT_DENIED;
 
-        if (notification.fullScreenIntent != null && ai.targetSdkVersion >= Build.VERSION_CODES.Q) {
+        if (notification.fullScreenIntent != null) {
             final boolean forceDemoteFsiToStickyHun = mFlagResolver.isEnabled(
                     SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE);
-
-            final boolean showStickyHunIfDenied = mFlagResolver.isEnabled(
-                    SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
-
             if (forceDemoteFsiToStickyHun) {
                 makeStickyHun(notification, pkg, userId);
-
-            } else if (showStickyHunIfDenied) {
-                final AttributionSource source = new AttributionSource.Builder(notificationUid)
-                        .setPackageName(pkg)
-                        .build();
-
-                final int permissionResult = mPermissionManager.checkPermissionForDataDelivery(
-                        Manifest.permission.USE_FULL_SCREEN_INTENT, source, /* message= */ null);
-
-                if (permissionResult != PermissionManager.PERMISSION_GRANTED) {
-                    makeStickyHun(notification, pkg, userId);
-                }
-
             } else {
-                int fullscreenIntentPermission = getContext().checkPermission(
-                        android.Manifest.permission.USE_FULL_SCREEN_INTENT, -1, notificationUid);
-
-                if (fullscreenIntentPermission != PERMISSION_GRANTED) {
-                    notification.fullScreenIntent = null;
-                    Slog.w(TAG, "Package " + pkg + ": Use of fullScreenIntent requires the"
-                            + "USE_FULL_SCREEN_INTENT permission");
+                final AttributionSource attributionSource =
+                        new AttributionSource.Builder(notificationUid).setPackageName(pkg).build();
+                final boolean showStickyHunIfDenied = mFlagResolver.isEnabled(
+                        SystemUiSystemPropertiesFlags.NotificationFlags
+                                .SHOW_STICKY_HUN_FOR_DENIED_FSI);
+                final boolean canUseFullScreenIntent = checkUseFullScreenIntentPermission(
+                        attributionSource, ai, showStickyHunIfDenied /* isAppOpPermission */,
+                        true /* forDataDelivery */);
+                if (!canUseFullScreenIntent) {
+                    if (showStickyHunIfDenied) {
+                        makeStickyHun(notification, pkg, userId);
+                    } else {
+                        notification.fullScreenIntent = null;
+                        Slog.w(TAG, "Package " + pkg + ": Use of fullScreenIntent requires the"
+                                + "USE_FULL_SCREEN_INTENT permission");
+                    }
                 }
             }
         }
@@ -6896,6 +6962,30 @@
                 ai.packageName) == AppOpsManager.MODE_ALLOWED;
     }
 
+    private boolean checkUseFullScreenIntentPermission(@NonNull AttributionSource attributionSource,
+            @NonNull ApplicationInfo applicationInfo, boolean isAppOpPermission,
+            boolean forDataDelivery) {
+        if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q) {
+            return true;
+        }
+        if (isAppOpPermission) {
+            final int permissionResult;
+            if (forDataDelivery) {
+                permissionResult = mPermissionManager.checkPermissionForDataDelivery(
+                        permission.USE_FULL_SCREEN_INTENT, attributionSource, /* message= */ null);
+            } else {
+                permissionResult = mPermissionManager.checkPermissionForPreflight(
+                        permission.USE_FULL_SCREEN_INTENT, attributionSource);
+            }
+            return permissionResult == PermissionManager.PERMISSION_GRANTED;
+        } else {
+            final int permissionResult = getContext().checkPermission(
+                    permission.USE_FULL_SCREEN_INTENT, attributionSource.getPid(),
+                    attributionSource.getUid());
+            return permissionResult == PERMISSION_GRANTED;
+        }
+    }
+
     private void checkRemoteViews(String pkg, String tag, int id, Notification notification) {
         if (removeRemoteView(pkg, tag, id, notification.contentView)) {
             notification.contentView = null;
@@ -7080,8 +7170,8 @@
                 }
             }
 
-            // limit the number of non-fgs outstanding notificationrecords an app can have
-            if (!n.isForegroundService()) {
+            // limit the number of non-fgs/uij outstanding notificationrecords an app can have
+            if (!n.isFgsOrUij()) {
                 int count = getNotificationCount(pkg, userId, id, tag);
                 if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                     mUsageStats.registerOverCountQuota(pkg);
@@ -7441,7 +7531,8 @@
                                     return false;
                                 }
                             } else if (mReason == REASON_APP_CANCEL) {
-                                if ((flags & FLAG_FOREGROUND_SERVICE) != 0) {
+                                if ((flags & FLAG_FOREGROUND_SERVICE) != 0
+                                        || (flags & FLAG_USER_INITIATED_JOB) != 0) {
                                     return false;
                                 }
                             }
@@ -7994,7 +8085,7 @@
         }
 
         FlagChecker childrenFlagChecker = (flags) -> {
-            if ((flags & FLAG_FOREGROUND_SERVICE) != 0) {
+            if ((flags & FLAG_FOREGROUND_SERVICE) != 0 || (flags & FLAG_USER_INITIATED_JOB) != 0) {
                 return false;
             }
             return true;
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index 9e91875..ffe33a8 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -383,6 +383,7 @@
         public int numWithBigText;
         public int numWithBigPicture;
         public int numForegroundService;
+        public int numUserInitiatedJob;
         public int numOngoing;
         public int numAutoCancel;
         public int numWithLargeIcon;
@@ -433,6 +434,10 @@
                 numForegroundService++;
             }
 
+            if ((n.flags & Notification.FLAG_USER_INITIATED_JOB) != 0) {
+                numUserInitiatedJob++;
+            }
+
             if ((n.flags & Notification.FLAG_ONGOING_EVENT) != 0) {
                 numOngoing++;
             }
@@ -516,6 +521,7 @@
             maybeCount("note_big_text", (numWithBigText - previous.numWithBigText));
             maybeCount("note_big_pic", (numWithBigPicture - previous.numWithBigPicture));
             maybeCount("note_fg", (numForegroundService - previous.numForegroundService));
+            maybeCount("note_uij", (numUserInitiatedJob - previous.numUserInitiatedJob));
             maybeCount("note_ongoing", (numOngoing - previous.numOngoing));
             maybeCount("note_auto", (numAutoCancel - previous.numAutoCancel));
             maybeCount("note_large_icon", (numWithLargeIcon - previous.numWithLargeIcon));
@@ -550,6 +556,7 @@
             previous.numWithBigText = numWithBigText;
             previous.numWithBigPicture = numWithBigPicture;
             previous.numForegroundService = numForegroundService;
+            previous.numUserInitiatedJob = numUserInitiatedJob;
             previous.numOngoing = numOngoing;
             previous.numAutoCancel = numAutoCancel;
             previous.numWithLargeIcon = numWithLargeIcon;
@@ -645,6 +652,8 @@
             output.append(indentPlusTwo);
             output.append("numForegroundService=").append(numForegroundService).append("\n");
             output.append(indentPlusTwo);
+            output.append("numUserInitiatedJob=").append(numUserInitiatedJob).append("\n");
+            output.append(indentPlusTwo);
             output.append("numOngoing=").append(numOngoing).append("\n");
             output.append(indentPlusTwo);
             output.append("numAutoCancel=").append(numAutoCancel).append("\n");
@@ -701,6 +710,7 @@
             maybePut(dump, "numWithBigText", numWithBigText);
             maybePut(dump, "numWithBigPicture", numWithBigPicture);
             maybePut(dump, "numForegroundService", numForegroundService);
+            maybePut(dump, "numUserInitiatedJob", numUserInitiatedJob);
             maybePut(dump, "numOngoing", numOngoing);
             maybePut(dump, "numAutoCancel", numAutoCancel);
             maybePut(dump, "numWithLargeIcon", numWithLargeIcon);
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 35b94e7..88d23ce 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -27,6 +27,7 @@
 import android.service.notification.IConditionProvider;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeDiff;
 import android.util.Log;
 import android.util.Slog;
 
@@ -146,13 +147,13 @@
 
     public static void traceConfig(String reason, ZenModeConfig oldConfig,
             ZenModeConfig newConfig) {
-        ZenModeConfig.Diff diff = ZenModeConfig.diff(oldConfig, newConfig);
-        if (diff.isEmpty()) {
+        ZenModeDiff.ConfigDiff diff = new ZenModeDiff.ConfigDiff(oldConfig, newConfig);
+        if (diff == null || !diff.hasDiff()) {
             append(TYPE_CONFIG, reason + " no changes");
         } else {
             append(TYPE_CONFIG, reason
                     + ",\n" + (newConfig != null ? newConfig.toString() : null)
-                    + ",\n" + ZenModeConfig.diff(oldConfig, newConfig));
+                    + ",\n" + diff);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
index cae7079..82b585d 100644
--- a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
+++ b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
@@ -65,7 +65,7 @@
     private static final String AVC_PREFIX = "type=" + AUDIT_AVC + " ";
 
     private static final Pattern EXECUTE_NATIVE_AUDIT_PATTERN =
-            Pattern.compile(".*\\bavc: granted \\{ execute(?:_no_trans|) \\} .*"
+            Pattern.compile(".*\\bavc: +granted +\\{ execute(?:_no_trans|) \\} .*"
                     + "\\bpath=(?:\"([^\" ]*)\"|([0-9A-F]+)) .*"
                     + "\\bscontext=u:r:untrusted_app(?:_25|_27)?:.*"
                     + "\\btcontext=u:object_r:app_data_file:.*"
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index c29e4d7..52fdbda 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -606,6 +606,7 @@
         final Computer snapshot = snapshot();
         // Return null for InstantApps.
         if (snapshot.getInstantAppPackageName(Binder.getCallingUid()) != null) {
+            Log.w(PackageManagerService.TAG, "Returning null PackageInstaller for InstantApps");
             return null;
         }
         return mInstallerService;
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 5f424ed..b8c2880 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2265,10 +2265,26 @@
                     // The caller explicitly specified INSTALL_ALL_USERS flag.
                     // Thus, updating the settings to install the app for all users.
                     for (int currentUserId : allUsers) {
-                        ps.setInstalled(true, currentUserId);
-                        if (!installRequest.isApplicationEnabledSettingPersistent()) {
-                            ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, currentUserId,
-                                    installerPackageName);
+                        // If the app is already installed for the currentUser,
+                        // keep it as installed as we might be updating the app at this place.
+                        // If not currently installed, check if the currentUser is restricted by
+                        // DISALLOW_INSTALL_APPS or DISALLOW_DEBUGGING_FEATURES device policy.
+                        // Install / update the app if the user isn't restricted. Skip otherwise.
+                        final boolean installedForCurrentUser = ArrayUtils.contains(
+                                installedForUsers, currentUserId);
+                        final boolean restrictedByPolicy =
+                                mPm.isUserRestricted(currentUserId,
+                                        UserManager.DISALLOW_INSTALL_APPS)
+                                || mPm.isUserRestricted(currentUserId,
+                                        UserManager.DISALLOW_DEBUGGING_FEATURES);
+                        if (installedForCurrentUser || !restrictedByPolicy) {
+                            ps.setInstalled(true, currentUserId);
+                            if (!installRequest.isApplicationEnabledSettingPersistent()) {
+                                ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, currentUserId,
+                                        installerPackageName);
+                            }
+                        } else {
+                            ps.setInstalled(false, currentUserId);
                         }
                     }
                 }
@@ -3588,6 +3604,11 @@
                 // remove the package from the system and re-scan it without any
                 // special privileges
                 mRemovePackageHelper.removePackage(pkg, true);
+                PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+                if (ps != null) {
+                    ps.getPkgState().setUpdatedSystemApp(false);
+                }
+
                 try {
                     final File codePath = new File(pkg.getPath());
                     synchronized (mPm.mInstallLock) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 84e9c3f..c40895d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -677,7 +677,8 @@
                 ? params.installerPackageName : installerPackageName;
 
         if (PackageManagerServiceUtils.isRootOrShell(callingUid)
-                || PackageInstallerSession.isSystemDataLoaderInstallation(params)) {
+                || PackageInstallerSession.isSystemDataLoaderInstallation(params)
+                || PackageManagerServiceUtils.isAdoptedShell(callingUid, mContext)) {
             params.installFlags |= PackageManager.INSTALL_FROM_ADB;
             // adb installs can override the installingPackageName, but not the
             // initiatingPackageName
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index a8ec256..72a0b57 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1461,7 +1461,10 @@
             @NonNull IOnChecksumsReadyListener onChecksumsReadyListener) {
         assertCallerIsOwnerRootOrVerifier();
         final File file = new File(stageDir, name);
-        final String installerPackageName = getInstallSource().mInitiatingPackageName;
+        final String installerPackageName = PackageManagerServiceUtils.isInstalledByAdb(
+                getInstallSource().mInitiatingPackageName)
+                ? getInstallSource().mInstallerPackageName
+                : getInstallSource().mInitiatingPackageName;
         try {
             mPm.requestFileChecksums(file, installerPackageName, optional, required,
                     trustedInstallers, onChecksumsReadyListener);
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 42538f3..db997d8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -36,6 +36,7 @@
 import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
 import static com.android.server.pm.PackageManagerService.TAG;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1389,6 +1390,14 @@
     }
 
     /**
+     * Check if a UID is non-system UID adopted shell permission.
+     */
+    public static boolean isAdoptedShell(int uid, Context context) {
+        return uid != Process.SYSTEM_UID && context.checkCallingOrSelfPermission(
+                Manifest.permission.USE_SYSTEM_DATA_LOADERS) == PackageManager.PERMISSION_GRANTED;
+    }
+
+    /**
      * Check if a UID is system UID or shell's UID.
      */
     public static boolean isRootOrShell(int uid) {
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index ad77ef7..9127a93 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -187,6 +187,9 @@
             if (changed) {
                 changedPackagesList.add(packageName);
                 changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+            } else {
+                Slog.w(TAG, "No change is needed for package: " + packageName
+                        + ". Skipping suspending/un-suspending.");
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7603c45..b749126 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2609,7 +2609,7 @@
             }
         }
         if (scheduleWriteUser) {
-            scheduleWriteUser(userData);
+            scheduleWriteUser(userId);
         }
     }
 
@@ -2919,7 +2919,7 @@
                     != newBaseRestrictions);
 
             if (mBaseUserRestrictions.updateRestrictions(userId, newBaseRestrictions)) {
-                scheduleWriteUser(getUserDataNoChecks(userId));
+                scheduleWriteUser(userId);
             }
         }
 
@@ -2995,7 +2995,7 @@
     @GuardedBy("mRestrictionsLock")
     private void applyUserRestrictionsLR(@UserIdInt int userId) {
         updateUserRestrictionsInternalLR(null, userId);
-        scheduleWriteUser(getUserDataNoChecks(userId));
+        scheduleWriteUser(userId);
     }
 
     @GuardedBy("mRestrictionsLock")
@@ -4146,14 +4146,14 @@
         }
     }
 
-    private void scheduleWriteUser(UserData userData) {
+    private void scheduleWriteUser(@UserIdInt int userId) {
         if (DBG) {
             debug("scheduleWriteUser");
         }
         // No need to wrap it within a lock -- worst case, we'll just post the same message
         // twice.
-        if (!mHandler.hasMessages(WRITE_USER_MSG, userData)) {
-            Message msg = mHandler.obtainMessage(WRITE_USER_MSG, userData);
+        if (!mHandler.hasMessages(WRITE_USER_MSG, userId)) {
+            Message msg = mHandler.obtainMessage(WRITE_USER_MSG, userId);
             mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY);
         }
     }
@@ -4169,7 +4169,7 @@
             // Something went wrong, schedule full rewrite.
             UserData userData = getUserDataNoChecks(userId);
             if (userData != null) {
-                scheduleWriteUser(userData);
+                scheduleWriteUser(userId);
             }
         });
     }
@@ -6348,7 +6348,7 @@
             userData.info.lastLoggedInTime = now;
         }
         userData.info.lastLoggedInFingerprint = PackagePartitions.FINGERPRINT;
-        scheduleWriteUser(userData);
+        scheduleWriteUser(userId);
     }
 
     /**
@@ -6518,7 +6518,7 @@
 
     private void setLastEnteredForegroundTimeToNow(@NonNull UserData userData) {
         userData.mLastEnteredForegroundTimeMillis = System.currentTimeMillis();
-        scheduleWriteUser(userData);
+        scheduleWriteUser(userData.info.id);
     }
 
     @Override
@@ -6817,7 +6817,7 @@
                 case WRITE_USER_MSG:
                     removeMessages(WRITE_USER_MSG, msg.obj);
                     synchronized (mPackagesLock) {
-                        int userId = ((UserData) msg.obj).info.id;
+                        int userId = (int) msg.obj;
                         UserData userData = getUserDataNoChecks(userId);
                         if (userData != null) {
                             writeUserLP(userData);
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index c9ebeae..5015985 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -361,7 +361,8 @@
         }
         final int verifierUserId = verifierUser.getIdentifier();
 
-        List<String> requiredVerifierPackages = Arrays.asList(mPm.mRequiredVerifierPackages);
+        List<String> requiredVerifierPackages = new ArrayList<>(
+                Arrays.asList(mPm.mRequiredVerifierPackages));
         boolean requiredVerifierPackagesOverridden = false;
 
         // Allow verifier override for ADB installations which could already be unverified using
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index e5e32f0..4d2b119 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -206,8 +206,6 @@
     static {
         SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
         SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
-        SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE);
-        SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND);
     }
 
     private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index df0192c..813c7f4 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1330,9 +1330,7 @@
                     // Bg location is one-off runtime modifier permission and has no app op
                     if (sPlatformPermissions.containsKey(permission)
                             && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)
-                            && !Manifest.permission.BODY_SENSORS_BACKGROUND.equals(permission)
-                            && !Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND
-                            .equals(permission)) {
+                            && !Manifest.permission.BODY_SENSORS_BACKGROUND.equals(permission)) {
                         Slog.wtf(LOG_TAG, "Platform runtime permission " + permission
                                 + " with no app op defined!");
                     }
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 401eac6..7a5664f 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -316,6 +316,7 @@
     private int resolveDatasourceOp(int code, int uid, @NonNull String packageName,
             @Nullable String attributionTag) {
         code = resolveRecordAudioOp(code, uid);
+        code = resolveSandboxedServiceOp(code, uid);
         if (attributionTag == null) {
             return code;
         }
@@ -439,6 +440,28 @@
         return code;
     }
 
+    private int resolveSandboxedServiceOp(int code, int uid) {
+        if (!Process.isIsolated(uid) // simple check which fails-fast for the common case
+                 || !(code == AppOpsManager.OP_RECORD_AUDIO || code == AppOpsManager.OP_CAMERA)) {
+            return code;
+        }
+        final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity =
+                mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity();
+        if (hotwordDetectionServiceIdentity != null
+                && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) {
+            // Upgrade the op such that no indicators is shown for camera or audio service. This
+            // will bypass the permission checking for the original OP_RECORD_AUDIO and OP_CAMERA.
+            switch (code) {
+                case AppOpsManager.OP_RECORD_AUDIO:
+                    return AppOpsManager.OP_RECORD_AUDIO_SANDBOXED;
+                case AppOpsManager.OP_CAMERA:
+                    return AppOpsManager.OP_CAMERA_SANDBOXED;
+            }
+        }
+        return code;
+    }
+
+
     private int resolveUid(int code, int uid) {
         // The HotwordDetectionService is an isolated service, which ordinarily cannot hold
         // permissions. So we allow it to assume the owning package identity for certain
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index b1b0c55..4a03628 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3569,6 +3569,7 @@
     private void dumpWallpaper(WallpaperData wallpaper, PrintWriter pw) {
         if (wallpaper == null) {
             pw.println(" (null entry)");
+            return;
         }
         pw.print(" User "); pw.print(wallpaper.userId);
         pw.print(": id="); pw.print(wallpaper.wallpaperId);
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
index 83804f7..32f7b96 100644
--- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -466,8 +466,7 @@
     }
 
     boolean isAnimatingByRecents(@NonNull Task task) {
-        return task.isAnimatingByRecents()
-                || mService.mAtmService.getTransitionController().inRecentsTransition(task);
+        return task.isAnimatingByRecents();
     }
 
     void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index fa3a186..f300113 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -25,6 +25,8 @@
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
 
+import static com.android.internal.util.DumpUtils.dumpSparseArray;
+import static com.android.internal.util.DumpUtils.dumpSparseArrayValues;
 import static com.android.server.accessibility.AccessibilityTraceFileProto.ENTRY;
 import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER;
 import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER_H;
@@ -542,15 +544,12 @@
     }
 
     void dump(PrintWriter pw, String prefix) {
-        for (int i = 0; i < mDisplayMagnifiers.size(); i++) {
-            final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.valueAt(i);
-            if (displayMagnifier != null) {
-                displayMagnifier.dump(pw, prefix
-                        + "Magnification display# " + mDisplayMagnifiers.keyAt(i));
-            }
-        }
-        pw.println(prefix
-                + "mWindowsForAccessibilityObserver=" + mWindowsForAccessibilityObserver);
+        dumpSparseArray(pw, prefix, mDisplayMagnifiers, "magnification display",
+                (index, key) -> pw.printf("%sDisplay #%d:", prefix + "  ", key),
+                dm -> dm.dump(pw, ""));
+        dumpSparseArrayValues(pw, prefix, mWindowsForAccessibilityObserver,
+                "windows for accessibility observer");
+        mAccessibilityWindowsPopulator.dump(pw, prefix);
     }
 
     void onFocusChanged(InputTarget lastTarget, InputTarget newTarget) {
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index 21b241a..70f2007 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static com.android.internal.util.DumpUtils.KeyDumper;
+import static com.android.internal.util.DumpUtils.ValueDumper;
+import static com.android.internal.util.DumpUtils.dumpSparseArray;
 import static com.android.server.wm.utils.RegionUtils.forEachRect;
 
 import android.annotation.NonNull;
@@ -40,6 +43,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -562,6 +566,35 @@
         notifyWindowsChanged(displayIdsForWindowsChanged);
     }
 
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.println("AccessibilityWindowsPopulator");
+        String prefix2 = prefix + "  ";
+
+        pw.print(prefix2); pw.print("mWindowsNotificationEnabled: ");
+        pw.println(mWindowsNotificationEnabled);
+
+        if (mVisibleWindows.isEmpty()) {
+            pw.print(prefix2); pw.println("No visible windows");
+        } else {
+            pw.print(prefix2); pw.print(mVisibleWindows.size());
+            pw.print(" visible windows: "); pw.println(mVisibleWindows);
+        }
+        KeyDumper noKeyDumper = (i, k) -> {}; // display id is already shown on value;
+        KeyDumper displayDumper = (i, d) -> pw.printf("%sDisplay #%d: ", prefix, d);
+        // Ideally magnificationSpecDumper should use spec.dump(pw), but there is no such method
+        ValueDumper<MagnificationSpec> magnificationSpecDumper = spec -> pw.print(spec);
+
+        dumpSparseArray(pw, prefix2, mDisplayInfos, "display info", noKeyDumper, d -> pw.print(d));
+        dumpSparseArray(pw, prefix2, mInputWindowHandlesOnDisplays, "window handles on display",
+                displayDumper, list -> pw.print(list));
+        dumpSparseArray(pw, prefix2, mMagnificationSpecInverseMatrix, "magnification spec matrix",
+                noKeyDumper, matrix -> matrix.dump(pw));
+        dumpSparseArray(pw, prefix2, mCurrentMagnificationSpec, "current magnification spec",
+                noKeyDumper, magnificationSpecDumper);
+        dumpSparseArray(pw, prefix2, mPreviousMagnificationSpec, "previous magnification spec",
+                noKeyDumper, magnificationSpecDumper);
+    }
+
     @GuardedBy("mLock")
     private void releaseResources() {
         mInputWindowHandlesOnDisplays.clear();
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index ff1c28a..6214440 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -78,8 +78,6 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.ParceledListSlice;
-import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.os.Binder;
 import android.os.Bundle;
@@ -1645,18 +1643,15 @@
                 launchedFromHome = root.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME);
             }
 
-            // If the activity is one of the main entry points for the application, then we should
+            // If the activity was launched directly from the home screen, then we should
             // refrain from finishing the activity and instead move it to the back to keep it in
             // memory. The requirements for this are:
             //   1. The activity is the last running activity in the task.
             //   2. The current activity is the base activity for the task.
-            //   3. a. If the activity was launched by the home process, we trust that its intent
-            //         was resolved, so we check if the it is a main intent for the application.
-            //      b. Otherwise, we query Package Manager to verify whether the activity is a
-            //         launcher activity for the application.
+            //   3. The activity was launched by the home process, and is one of the main entry
+            //      points for the application.
             if (baseActivityIntent != null && isLastRunningActivity
-                    && ((launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent))
-                        || isLauncherActivity(baseActivityIntent.getComponent()))) {
+                    && launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent)) {
                 moveActivityTaskToBack(token, true /* nonRoot */);
                 return;
             }
@@ -1668,31 +1663,6 @@
         }
     }
 
-    /**
-     * Queries PackageManager to see if the given activity is one of the main entry point for the
-     * application. This should not be called with the WM lock held.
-     */
-    @SuppressWarnings("unchecked")
-    private boolean isLauncherActivity(@NonNull ComponentName activity) {
-        final Intent queryIntent = new Intent(Intent.ACTION_MAIN);
-        queryIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-        queryIntent.setPackage(activity.getPackageName());
-        try {
-            final ParceledListSlice<ResolveInfo> resolved =
-                    mService.getPackageManager().queryIntentActivities(
-                            queryIntent, null, 0, mContext.getUserId());
-            if (resolved == null) return false;
-            for (final ResolveInfo ri : resolved.getList()) {
-                if (ri.getComponentInfo().getComponentName().equals(activity)) {
-                    return true;
-                }
-            }
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to query intent activities", e);
-        }
-        return false;
-    }
-
     @Override
     public void enableTaskLocaleOverride(IBinder token) {
         if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 8346e7c..1bcc05e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3522,7 +3522,8 @@
 
             final boolean endTask = task.getTopNonFinishingActivity() == null
                     && !task.isClearingToReuseTask();
-            mTransitionController.requestCloseTransitionIfNeeded(endTask ? task : this);
+            final Transition newTransition =
+                    mTransitionController.requestCloseTransitionIfNeeded(endTask ? task : this);
             if (isState(RESUMED)) {
                 if (endTask) {
                     mAtmService.getTaskChangeNotificationController().notifyTaskRemovalStarted(
@@ -3543,8 +3544,7 @@
                 // the best capture timing (e.g. IME window capture),
                 // No need additional task capture while task is controlled by RecentsAnimation.
                 if (mAtmService.mWindowManager.mTaskSnapshotController != null
-                        && !(task.isAnimatingByRecents()
-                                || mTransitionController.inRecentsTransition(task))) {
+                        && !task.isAnimatingByRecents()) {
                     final ArraySet<Task> tasks = Sets.newArraySet(task);
                     mAtmService.mWindowManager.mTaskSnapshotController.snapshotTasks(tasks);
                     mAtmService.mWindowManager.mTaskSnapshotController
@@ -3576,7 +3576,16 @@
             } else if (!isState(PAUSING)) {
                 if (mVisibleRequested) {
                     // Prepare and execute close transition.
-                    prepareActivityHideTransitionAnimation();
+                    if (mTransitionController.isShellTransitionsEnabled()) {
+                        setVisibility(false);
+                        if (newTransition != null) {
+                            // This is a transition specifically for this close operation, so set
+                            // ready now.
+                            newTransition.setReady(mDisplayContent, true);
+                        }
+                    } else {
+                        prepareActivityHideTransitionAnimation();
+                    }
                 }
 
                 final boolean removedActivity = completeFinishing("finishIfPossible") == null;
@@ -7917,6 +7926,8 @@
 
         mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged(
                 task.mTaskId, requestedOrientation);
+
+        mDisplayContent.getDisplayRotation().onSetRequestedOrientation();
     }
 
     /*
@@ -9760,6 +9771,7 @@
      * directly with keeping its record.
      */
     void restartProcessIfVisible() {
+        if (finishing) return;
         Slog.i(TAG, "Request to restart process of " + this);
 
         // Reset the existing override configuration so it can be updated according to the latest
@@ -10522,11 +10534,6 @@
 
     @Override
     boolean isSyncFinished() {
-        if (task != null && mTransitionController.isTransientHide(task)) {
-            // The activity keeps visibleRequested but may be hidden later, so no need to wait for
-            // it to be drawn.
-            return true;
-        }
         if (!super.isSyncFinished()) return false;
         if (mDisplayContent != null && mDisplayContent.mUnknownAppVisibilityController
                 .isVisibilityUnknown(this)) {
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 7c1e907..bfe2986 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -577,7 +577,6 @@
             final Transition transition = controller.getCollectingTransition();
             if (transition != null) {
                 transition.setRemoteAnimationApp(r.app.getThread());
-                controller.collect(task);
                 controller.setTransientLaunch(r, TaskDisplayArea.getRootTaskAbove(rootTask));
             }
             task.moveToFront("startExistingRecents");
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index ea2765d..e7af22d 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1605,6 +1605,8 @@
             transitionController.requestStartTransition(newTransition,
                     mTargetTask == null ? started.getTask() : mTargetTask,
                     remoteTransition, null /* displayChange */);
+        } else if (result == START_SUCCESS && mStartActivity.isState(RESUMED)) {
+            // Do nothing if the activity is started and is resumed directly.
         } else if (isStarted) {
             // Make the collecting transition wait until this request is ready.
             transitionController.setReady(started, false);
@@ -2545,6 +2547,7 @@
         mAvoidMoveToFront = false;
         mFrozeTaskList = false;
         mTransientLaunch = false;
+        mPriorAboveTask = null;
         mDisplayLockAndOccluded = false;
 
         mVoiceSession = null;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index d2fc393..16f4612 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -831,7 +831,7 @@
     private final Runnable mUpdateOomAdjRunnable = new Runnable() {
         @Override
         public void run() {
-            mAmInternal.updateOomAdj();
+            mAmInternal.updateOomAdj(ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY);
         }
     };
 
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 48cf567..d916a1b 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -23,6 +23,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Handler;
 import android.os.Trace;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -172,7 +173,7 @@
                         if (ran) {
                             return;
                         }
-                        mWm.mH.removeCallbacks(this);
+                        mHandler.removeCallbacks(this);
                         ran = true;
                         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
                         for (WindowContainer wc : wcAwaitingCommit) {
@@ -199,13 +200,13 @@
             };
             CommitCallback callback = new CommitCallback();
             merged.addTransactionCommittedListener((r) -> { r.run(); }, callback::onCommitted);
-            mWm.mH.postDelayed(callback, BLAST_TIMEOUT_DURATION);
+            mHandler.postDelayed(callback, BLAST_TIMEOUT_DURATION);
 
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady");
             mListener.onTransactionReady(mSyncId, merged);
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             mActiveSyncs.remove(mSyncId);
-            mWm.mH.removeCallbacks(mOnTimeout);
+            mHandler.removeCallbacks(mOnTimeout);
 
             // Immediately start the next pending sync-transaction if there is one.
             if (mActiveSyncs.size() == 0 && !mPendingSyncSets.isEmpty()) {
@@ -216,7 +217,7 @@
                     throw new IllegalStateException("Pending Sync Set didn't start a sync.");
                 }
                 // Post this so that the now-playing transition setup isn't interrupted.
-                mWm.mH.post(() -> {
+                mHandler.post(() -> {
                     synchronized (mWm.mGlobalLock) {
                         pt.mApplySync.run();
                     }
@@ -228,7 +229,7 @@
             if (mReady == ready) {
                 return;
             }
-            ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready", mSyncId);
+            ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready %b", mSyncId, ready);
             mReady = ready;
             if (!ready) return;
             mWm.mWindowPlacerLocked.requestTraversal();
@@ -269,6 +270,7 @@
     }
 
     private final WindowManagerService mWm;
+    private final Handler mHandler;
     private int mNextSyncId = 0;
     private final SparseArray<SyncGroup> mActiveSyncs = new SparseArray<>();
 
@@ -280,7 +282,13 @@
     private final ArrayList<PendingSyncSet> mPendingSyncSets = new ArrayList<>();
 
     BLASTSyncEngine(WindowManagerService wms) {
+        this(wms, wms.mH);
+    }
+
+    @VisibleForTesting
+    BLASTSyncEngine(WindowManagerService wms, Handler mainHandler) {
         mWm = wms;
+        mHandler = mainHandler;
     }
 
     /**
@@ -305,8 +313,8 @@
         if (mActiveSyncs.size() != 0) {
             // We currently only support one sync at a time, so start a new SyncGroup when there is
             // another may cause issue.
-            ProtoLog.w(WM_DEBUG_SYNC_ENGINE,
-                    "SyncGroup %d: Started when there is other active SyncGroup", s.mSyncId);
+            Slog.e(TAG, "SyncGroup " + s.mSyncId
+                    + ": Started when there is other active SyncGroup");
         }
         mActiveSyncs.put(s.mSyncId, s);
         ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s",
@@ -325,7 +333,7 @@
 
     @VisibleForTesting
     void scheduleTimeout(SyncGroup s, long timeoutMs) {
-        mWm.mH.postDelayed(s.mOnTimeout, timeoutMs);
+        mHandler.postDelayed(s.mOnTimeout, timeoutMs);
     }
 
     void addToSyncSet(int id, WindowContainer wc) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 587e720..7b562b0 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -286,8 +286,8 @@
                                 currentActivity.getCustomAnimation(false/* open */);
                         if (customAppTransition != null) {
                             infoBuilder.setCustomAnimation(currentActivity.packageName,
-                                    customAppTransition.mExitAnim,
                                     customAppTransition.mEnterAnim,
+                                    customAppTransition.mExitAnim,
                                     customAppTransition.mBackgroundColor);
                         }
                     }
@@ -585,14 +585,14 @@
      * The closing target should only exist in close list, but the opening target can be either in
      * open or close list.
      */
-    void onTransactionReady(Transition transition) {
+    void onTransactionReady(Transition transition, ArrayList<Transition.ChangeInfo> targets) {
         if (!isMonitoringTransition()) {
             return;
         }
-        final ArraySet<WindowContainer> targets = transition.mParticipants;
         for (int i = targets.size() - 1; i >= 0; --i) {
-            final WindowContainer wc = targets.valueAt(i);
-            if (wc.asActivityRecord() == null && wc.asTask() == null) {
+            final WindowContainer wc = targets.get(i).mContainer;
+            if (wc.asActivityRecord() == null && wc.asTask() == null
+                    && wc.asTaskFragment() == null) {
                 continue;
             }
             // WC can be visible due to setLaunchBehind
@@ -605,6 +605,9 @@
         final boolean matchAnimationTargets = isWaitBackTransition()
                 && (transition.mType == TRANSIT_CLOSE || transition.mType == TRANSIT_TO_BACK)
                 && mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps);
+        ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
+                "onTransactionReady, opening: %s, closing: %s, animating: %s, match: %b",
+                mTmpOpenApps, mTmpCloseApps, mAnimationHandler, matchAnimationTargets);
         if (!matchAnimationTargets) {
             mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps);
         } else {
@@ -829,10 +832,16 @@
             if (!mComposed) {
                 return false;
             }
+
+            // WC must be ActivityRecord in legacy transition, but it also can be Task or
+            // TaskFragment when using Shell transition.
+            // Open target: Can be Task or ActivityRecord or TaskFragment
+            // Close target: Limit to the top activity for now, to reduce the chance of misjudgment.
             final WindowContainer target = open ? mOpenAdaptor.mTarget : mCloseAdaptor.mTarget;
             if (mSwitchType == TASK_SWITCH) {
                 return  wc == target
-                        || (wc.asTask() != null && wc.hasChild(target));
+                        || (wc.asTask() != null && wc.hasChild(target))
+                        || (wc.asActivityRecord() != null && target.hasChild(wc));
             } else if (mSwitchType == ACTIVITY_SWITCH) {
                 return wc == target || (wc.asTaskFragment() != null && wc.hasChild(target));
             }
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 13a1cb6..c6db8a7 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -126,6 +126,9 @@
         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.
          */
@@ -260,11 +263,16 @@
      * {@link WindowContainer#prepareSurfaces}. After calling this, the container should
      * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
      * a chance to request dims to continue.
+     * @return Non-null dim bounds if the dimmer is showing.
      */
-    void resetDimStates() {
-        if (mDimState != null && !mDimState.mDontReset) {
+    Rect resetDimStates() {
+        if (mDimState == null) {
+            return null;
+        }
+        if (!mDimState.mDontReset) {
             mDimState.mDimming = false;
         }
+        return mDimState.mDimBounds;
     }
 
     void dontAnimateExit() {
@@ -275,13 +283,13 @@
 
     /**
      * Call after invoking {@link WindowContainer#prepareSurfaces} on children as
-     * described in {@link #resetDimStates}.
+     * described in {@link #resetDimStates}. The dim bounds returned by {@link #resetDimStates}
+     * should be set before calling this method.
      *
      * @param t      A transaction in which to update the dims.
-     * @param bounds The bounds at which to dim.
      * @return true if any Dims were updated.
      */
-    boolean updateDims(SurfaceControl.Transaction t, Rect bounds) {
+    boolean updateDims(SurfaceControl.Transaction t) {
         if (mDimState == null) {
             return false;
         }
@@ -297,6 +305,7 @@
             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());
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 00299c2..26f56a2 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -767,7 +767,6 @@
      */
     static class Dimmable extends DisplayArea<DisplayArea> {
         private final Dimmer mDimmer = new Dimmer(this);
-        private final Rect mTmpDimBoundsRect = new Rect();
 
         Dimmable(WindowManagerService wms, Type type, String name, int featureId) {
             super(wms, type, name, featureId);
@@ -780,11 +779,13 @@
 
         @Override
         void prepareSurfaces() {
-            mDimmer.resetDimStates();
+            final Rect dimBounds = mDimmer.resetDimStates();
             super.prepareSurfaces();
-            // Bounds need to be relative, as the dim layer is a child.
-            getBounds(mTmpDimBoundsRect);
-            mTmpDimBoundsRect.offsetTo(0 /* newLeft */, 0 /* newTop */);
+            if (dimBounds != null) {
+                // Bounds need to be relative, as the dim layer is a child.
+                getBounds(dimBounds);
+                dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */);
+            }
 
             // If SystemUI is dragging for recents, we want to reset the dim state so any dim layer
             // on the display level fades out.
@@ -792,8 +793,10 @@
                 mDimmer.resetDimStates();
             }
 
-            if (mDimmer.updateDims(getSyncTransaction(), mTmpDimBoundsRect)) {
-                scheduleAnimation();
+            if (dimBounds != null) {
+                if (mDimmer.updateDims(getSyncTransaction())) {
+                    scheduleAnimation();
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index bec58b8..c2bc459 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -775,6 +775,8 @@
      */
     DisplayWindowPolicyControllerHelper mDwpcHelper;
 
+    private final DisplayRotationReversionController mRotationReversionController;
+
     private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
         WindowStateAnimator winAnimator = w.mWinAnimator;
         final ActivityRecord activity = w.mActivityRecord;
@@ -1204,6 +1206,7 @@
                 mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
                             /* checkDeviceConfig */ false)
                         ? new DisplayRotationCompatPolicy(this) : null;
+        mRotationReversionController = new DisplayRotationReversionController(this);
 
         mInputMonitor = new InputMonitor(mWmService, this);
         mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
@@ -1333,6 +1336,10 @@
                 .show(mA11yOverlayLayer);
     }
 
+    DisplayRotationReversionController getRotationReversionController() {
+        return mRotationReversionController;
+    }
+
     boolean isReady() {
         // The display is ready when the system and the individual display are both ready.
         return mWmService.mDisplayReady && mDisplayReady;
@@ -1711,9 +1718,14 @@
     }
 
     private boolean updateOrientation(boolean forceUpdate) {
+        final WindowContainer prevOrientationSource = mLastOrientationSource;
         final int orientation = getOrientation();
         // The last orientation source is valid only after getOrientation.
         final WindowContainer orientationSource = getLastOrientationSource();
+        if (orientationSource != prevOrientationSource
+                && mRotationReversionController.isRotationReversionEnabled()) {
+            mRotationReversionController.updateForNoSensorOverride();
+        }
         final ActivityRecord r =
                 orientationSource != null ? orientationSource.asActivityRecord() : null;
         if (r != null) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 628f4d3..20048ce 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -33,6 +33,9 @@
 import static com.android.server.wm.DisplayRotationProto.LAST_ORIENTATION;
 import static com.android.server.wm.DisplayRotationProto.ROTATION;
 import static com.android.server.wm.DisplayRotationProto.USER_ROTATION;
+import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT;
+import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_HALF_FOLD;
+import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_NOSENSOR;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE;
@@ -97,6 +100,8 @@
     // config changes and unexpected jumps while folding the device to closed state.
     private static final int FOLDING_RECOMPUTE_CONFIG_DELAY_MS = 800;
 
+    private static final int ROTATION_UNDEFINED = -1;
+
     private static class RotationAnimationPair {
         @AnimRes
         int mEnter;
@@ -104,6 +109,9 @@
         int mExit;
     }
 
+    @Nullable
+    final FoldController mFoldController;
+
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
     private final DisplayPolicy mDisplayPolicy;
@@ -125,8 +133,6 @@
     private OrientationListener mOrientationListener;
     private StatusBarManagerInternal mStatusBarManagerInternal;
     private SettingsObserver mSettingsObserver;
-    @Nullable
-    private FoldController mFoldController;
     @NonNull
     private final DeviceStateController mDeviceStateController;
     @NonNull
@@ -189,6 +195,12 @@
      */
     private int mShowRotationSuggestions;
 
+    /**
+     * The most recent {@link Surface.Rotation} choice shown to the user for confirmation, or
+     * {@link #ROTATION_UNDEFINED}
+     */
+    private int mRotationChoiceShownToUserForConfirmation = ROTATION_UNDEFINED;
+
     private static final int ALLOW_ALL_ROTATIONS_UNDEFINED = -1;
     private static final int ALLOW_ALL_ROTATIONS_DISABLED = 0;
     private static final int ALLOW_ALL_ROTATIONS_ENABLED = 1;
@@ -291,7 +303,11 @@
             if (mSupportAutoRotation && mContext.getResources().getBoolean(
                     R.bool.config_windowManagerHalfFoldAutoRotateOverride)) {
                 mFoldController = new FoldController();
+            } else {
+                mFoldController = null;
             }
+        } else {
+            mFoldController = null;
         }
     }
 
@@ -349,6 +365,11 @@
         return -1;
     }
 
+    @VisibleForTesting
+    boolean useDefaultSettingsProvider() {
+        return isDefaultDisplay;
+    }
+
     /**
      * Updates the configuration which may have different values depending on current user, e.g.
      * runtime resource overlay.
@@ -894,7 +915,8 @@
 
     @VisibleForTesting
     void setUserRotation(int userRotationMode, int userRotation) {
-        if (isDefaultDisplay) {
+        mRotationChoiceShownToUserForConfirmation = ROTATION_UNDEFINED;
+        if (useDefaultSettingsProvider()) {
             // We'll be notified via settings listener, so we don't need to update internal values.
             final ContentResolver res = mContext.getContentResolver();
             final int accelerometerRotation =
@@ -1613,6 +1635,17 @@
         }
     }
 
+    /**
+     * Called from {@link ActivityRecord#setRequestedOrientation(int)}
+     */
+    void onSetRequestedOrientation() {
+        if (mCompatPolicyForImmersiveApps == null
+                || mRotationChoiceShownToUserForConfirmation == ROTATION_UNDEFINED) {
+            return;
+        }
+        mOrientationListener.onProposedRotationChanged(mRotationChoiceShownToUserForConfirmation);
+    }
+
     void dump(String prefix, PrintWriter pw) {
         pw.println(prefix + "DisplayRotation");
         pw.println(prefix + "  mCurrentAppOrientation="
@@ -1839,7 +1872,7 @@
                 return false;
             }
             if (mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) {
-                return !(isTabletop ^ mTabletopRotations.contains(mRotation));
+                return isTabletop == mTabletopRotations.contains(mRotation);
             }
             return true;
         }
@@ -1863,14 +1896,17 @@
             return mDeviceState == DeviceStateController.DeviceState.OPEN
                     && !mShouldIgnoreSensorRotation // Ignore if the hinge angle still moving
                     && mInHalfFoldTransition
-                    && mHalfFoldSavedRotation != -1 // Ignore if we've already reverted.
+                    && mDisplayContent.getRotationReversionController().isOverrideActive(
+                        REVERSION_TYPE_HALF_FOLD)
                     && mUserRotationMode
-                    == WindowManagerPolicy.USER_ROTATION_LOCKED; // Ignore if we're unlocked.
+                        == WindowManagerPolicy.USER_ROTATION_LOCKED; // Ignore if we're unlocked.
         }
 
         int revertOverriddenRotation() {
             int savedRotation = mHalfFoldSavedRotation;
             mHalfFoldSavedRotation = -1;
+            mDisplayContent.getRotationReversionController()
+                    .revertOverride(REVERSION_TYPE_HALF_FOLD);
             mInHalfFoldTransition = false;
             return savedRotation;
         }
@@ -1890,6 +1926,8 @@
                     && mDeviceState != DeviceStateController.DeviceState.HALF_FOLDED) {
                 // The device has transitioned to HALF_FOLDED state: save the current rotation and
                 // update the device rotation.
+                mDisplayContent.getRotationReversionController().beforeOverrideApplied(
+                        REVERSION_TYPE_HALF_FOLD);
                 mHalfFoldSavedRotation = mRotation;
                 mDeviceState = newState;
                 // Now mFoldState is set to HALF_FOLDED, the overrideFrozenRotation function will
@@ -2012,9 +2050,11 @@
             mService.mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
             dispatchProposedRotation(rotation);
             if (isRotationChoiceAllowed(rotation)) {
+                mRotationChoiceShownToUserForConfirmation = rotation;
                 final boolean isValid = isValidRotationChoice(rotation);
                 sendProposedRotationChangeToStatusBarInternal(rotation, isValid);
             } else {
+                mRotationChoiceShownToUserForConfirmation = ROTATION_UNDEFINED;
                 mService.updateRotation(false /* alwaysSendConfiguration */,
                         false /* forceRelayout */);
             }
@@ -2093,6 +2133,8 @@
             final int mHalfFoldSavedRotation;
             final boolean mInHalfFoldTransition;
             final DeviceStateController.DeviceState mDeviceState;
+            @Nullable final boolean[] mRotationReversionSlots;
+
             @Nullable final String mDisplayRotationCompatPolicySummary;
 
             Record(DisplayRotation dr, int fromRotation, int toRotation) {
@@ -2133,6 +2175,8 @@
                         ? null
                         : dc.mDisplayRotationCompatPolicy
                                 .getSummaryForDisplayRotationHistoryRecord();
+                mRotationReversionSlots =
+                        dr.mDisplayContent.getRotationReversionController().getSlotsCopy();
             }
 
             void dump(String prefix, PrintWriter pw) {
@@ -2158,6 +2202,12 @@
                 if (mDisplayRotationCompatPolicySummary != null) {
                     pw.println(prefix + mDisplayRotationCompatPolicySummary);
                 }
+                if (mRotationReversionSlots != null) {
+                    pw.println(prefix + " reversionSlots= NOSENSOR "
+                            + mRotationReversionSlots[REVERSION_TYPE_NOSENSOR] + ", CAMERA "
+                            + mRotationReversionSlots[REVERSION_TYPE_CAMERA_COMPAT] + " HALF_FOLD "
+                            + mRotationReversionSlots[REVERSION_TYPE_HALF_FOLD]);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index fb72d6c..ae93a94 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -33,6 +33,7 @@
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -156,6 +157,11 @@
     @ScreenOrientation
     int getOrientation() {
         mLastReportedOrientation = getOrientationInternal();
+        if (mLastReportedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+            rememberOverriddenOrientationIfNeeded();
+        } else {
+            restoreOverriddenOrientationIfNeeded();
+        }
         return mLastReportedOrientation;
     }
 
@@ -277,6 +283,34 @@
                 + " }";
     }
 
+    private void restoreOverriddenOrientationIfNeeded() {
+        if (!isOrientationOverridden()) {
+            return;
+        }
+        if (mDisplayContent.getRotationReversionController().revertOverride(
+                REVERSION_TYPE_CAMERA_COMPAT)) {
+            ProtoLog.v(WM_DEBUG_ORIENTATION,
+                    "Reverting orientation after camera compat force rotation");
+            // Reset last orientation source since we have reverted the orientation.
+            mDisplayContent.mLastOrientationSource = null;
+        }
+    }
+
+    private boolean isOrientationOverridden() {
+        return mDisplayContent.getRotationReversionController().isOverrideActive(
+                REVERSION_TYPE_CAMERA_COMPAT);
+    }
+
+    private void rememberOverriddenOrientationIfNeeded() {
+        if (!isOrientationOverridden()) {
+            mDisplayContent.getRotationReversionController().beforeOverrideApplied(
+                    REVERSION_TYPE_CAMERA_COMPAT);
+            ProtoLog.v(WM_DEBUG_ORIENTATION,
+                    "Saving original orientation before camera compat, last orientation is %d",
+                    mDisplayContent.getLastOrientation());
+        }
+    }
+
     // Refreshing only when configuration changes after rotation.
     private boolean shouldRefreshActivity(ActivityRecord activity, Configuration newConfig,
             Configuration lastReportedConfig) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotationReversionController.java b/services/core/java/com/android/server/wm/DisplayRotationReversionController.java
new file mode 100644
index 0000000..d3a8a82
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayRotationReversionController.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+
+import android.annotation.Nullable;
+import android.app.WindowConfiguration;
+import android.content.ActivityInfoProto;
+import android.view.Surface;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.policy.WindowManagerPolicy;
+
+/**
+ * Defines the behavior of reversion from device rotation overrides.
+ *
+ * <p>There are 3 override types:
+ * <ol>
+ *  <li>The top application has {@link SCREEN_ORIENTATION_NOSENSOR} set and is rotated to
+ *  {@link ROTATION_0}.
+ *  <li>Camera compat treatment has rotated the app {@link DisplayRotationCompatPolicy}.
+ *  <li>The device is half-folded and has auto-rotate is temporarily enabled.
+ * </ol>
+ *
+ * <p>Before an override is enabled, a component should call {@code beforeOverrideApplied}. When
+ * it wishes to revert, it should call {@code revertOverride}. The user rotation will be restored
+ * if there are no other overrides present.
+ */
+final class DisplayRotationReversionController {
+
+    static final int REVERSION_TYPE_NOSENSOR = 0;
+    static final int REVERSION_TYPE_CAMERA_COMPAT = 1;
+    static final int REVERSION_TYPE_HALF_FOLD = 2;
+    private static final int NUM_SLOTS = 3;
+
+    @Surface.Rotation
+    private int mUserRotationOverridden = WindowConfiguration.ROTATION_UNDEFINED;
+    @WindowManagerPolicy.UserRotationMode
+    private int mUserRotationModeOverridden;
+
+    private final boolean[] mSlots = new boolean[NUM_SLOTS];
+    private final DisplayContent mDisplayContent;
+
+    DisplayRotationReversionController(DisplayContent content) {
+        mDisplayContent = content;
+    }
+
+    boolean isRotationReversionEnabled() {
+        return mDisplayContent.mDisplayRotationCompatPolicy != null
+                || mDisplayContent.getDisplayRotation().mFoldController != null
+                || mDisplayContent.getIgnoreOrientationRequest();
+    }
+
+    void beforeOverrideApplied(int slotIndex) {
+        if (mSlots[slotIndex]) return;
+        maybeSaveUserRotation();
+        mSlots[slotIndex] = true;
+    }
+
+    boolean isOverrideActive(int slotIndex) {
+        return mSlots[slotIndex];
+    }
+
+    @Nullable
+    boolean[] getSlotsCopy() {
+        return isRotationReversionEnabled() ? mSlots.clone() : null;
+    }
+
+    void updateForNoSensorOverride() {
+        if (!mSlots[REVERSION_TYPE_NOSENSOR]) {
+            if (isTopFullscreenActivityNoSensor()) {
+                ProtoLog.v(WM_DEBUG_ORIENTATION, "NOSENSOR override detected");
+                beforeOverrideApplied(REVERSION_TYPE_NOSENSOR);
+            }
+        } else {
+            if (!isTopFullscreenActivityNoSensor()) {
+                ProtoLog.v(WM_DEBUG_ORIENTATION, "NOSENSOR override is absent: reverting");
+                revertOverride(REVERSION_TYPE_NOSENSOR);
+            }
+        }
+    }
+
+    boolean isAnyOverrideActive() {
+        for (int i = 0; i < NUM_SLOTS; ++i) {
+            if (mSlots[i]) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean revertOverride(int slotIndex) {
+        if (!mSlots[slotIndex]) return false;
+        mSlots[slotIndex] = false;
+        if (isAnyOverrideActive()) {
+            ProtoLog.v(WM_DEBUG_ORIENTATION,
+                    "Other orientation overrides are in place: not reverting");
+            return false;
+        }
+        // Only override if the rotation is frozen and there are no other active slots.
+        if (mDisplayContent.getDisplayRotation().isRotationFrozen()) {
+            mDisplayContent.getDisplayRotation().setUserRotation(
+                    mUserRotationModeOverridden,
+                    mUserRotationOverridden);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private void maybeSaveUserRotation() {
+        if (!isAnyOverrideActive()) {
+            mUserRotationModeOverridden =
+                    mDisplayContent.getDisplayRotation().getUserRotationMode();
+            mUserRotationOverridden = mDisplayContent.getDisplayRotation().getUserRotation();
+        }
+    }
+
+    private boolean isTopFullscreenActivityNoSensor() {
+        final Task topFullscreenTask =
+                mDisplayContent.getTask(
+                        t -> t.getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
+        if (topFullscreenTask != null) {
+            final ActivityRecord topActivity =
+                    topFullscreenTask.topRunningActivity();
+            return topActivity != null && topActivity.getOrientation()
+                    == ActivityInfoProto.SCREEN_ORIENTATION_NOSENSOR;
+        }
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
index bb50372..bf511adf0 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
@@ -263,8 +263,8 @@
         boolean changed = !Objects.equals(params.mDisplayUniqueId, info.uniqueId);
         params.mDisplayUniqueId = info.uniqueId;
 
-        changed |= params.mWindowingMode != task.getTaskDisplayArea().getWindowingMode();
-        params.mWindowingMode = task.getTaskDisplayArea().getWindowingMode();
+        changed |= params.mWindowingMode != task.getWindowingMode();
+        params.mWindowingMode = task.getWindowingMode();
 
         if (task.mLastNonFullscreenBounds != null) {
             changed |= !Objects.equals(params.mBounds, task.mLastNonFullscreenBounds);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 93233dd..ff1deaf 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1597,11 +1597,10 @@
         inheritConfiguration(firstOpaqueActivityBeneath);
         mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation(
                 mActivityRecord, firstOpaqueActivityBeneath,
-                (opaqueConfig, transparentConfig) -> {
-                    final Configuration mutatedConfiguration =
-                            fromOriginalTranslucentConfig(transparentConfig);
+                (opaqueConfig, transparentOverrideConfig) -> {
+                    resetTranslucentOverrideConfig(transparentOverrideConfig);
                     final Rect parentBounds = parent.getWindowConfiguration().getBounds();
-                    final Rect bounds = mutatedConfiguration.windowConfiguration.getBounds();
+                    final Rect bounds = transparentOverrideConfig.windowConfiguration.getBounds();
                     final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds();
                     // We cannot use letterboxBounds directly here because the position relies on
                     // letterboxing. Using letterboxBounds directly, would produce a double offset.
@@ -1610,9 +1609,9 @@
                             parentBounds.top + letterboxBounds.height());
                     // We need to initialize appBounds to avoid NPE. The actual value will
                     // be set ahead when resolving the Configuration for the activity.
-                    mutatedConfiguration.windowConfiguration.setAppBounds(new Rect());
+                    transparentOverrideConfig.windowConfiguration.setAppBounds(new Rect());
                     inheritConfiguration(firstOpaqueActivityBeneath);
-                    return mutatedConfiguration;
+                    return transparentOverrideConfig;
                 });
     }
 
@@ -1691,20 +1690,16 @@
                 true /* traverseTopToBottom */));
     }
 
-    // When overriding translucent activities configuration we need to keep some of the
-    // original properties
-    private Configuration fromOriginalTranslucentConfig(Configuration translucentConfig) {
-        final Configuration configuration = new Configuration(translucentConfig);
+    /** Resets the screen size related fields so they can be resolved by requested bounds later. */
+    private static void resetTranslucentOverrideConfig(Configuration config) {
         // The values for the following properties will be defined during the configuration
         // resolution in {@link ActivityRecord#resolveOverrideConfiguration} using the
         // properties inherited from the first not finishing opaque activity beneath.
-        configuration.orientation = ORIENTATION_UNDEFINED;
-        configuration.screenWidthDp = configuration.compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED;
-        configuration.screenHeightDp =
-                configuration.compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED;
-        configuration.smallestScreenWidthDp =
-                configuration.compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
-        return configuration;
+        config.orientation = ORIENTATION_UNDEFINED;
+        config.screenWidthDp = config.compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED;
+        config.screenHeightDp = config.compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED;
+        config.smallestScreenWidthDp = config.compatSmallestScreenWidthDp =
+                SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
     }
 
     private void inheritConfiguration(ActivityRecord firstOpaque) {
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index b386665..f5079d3 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -338,7 +338,8 @@
         synchronized (mService.mGlobalLock) {
             final Task focusedStack = mService.getTopDisplayFocusedRootTask();
             final Task topTask = focusedStack != null ? focusedStack.getTopMostTask() : null;
-            resetFreezeTaskListReordering(topTask);
+            final Task reorderToEndTask = topTask != null && topTask.hasChild() ? topTask : null;
+            resetFreezeTaskListReordering(reorderToEndTask);
         }
     }
 
@@ -1511,7 +1512,7 @@
         // callbacks here.
         final Task removedTask = mTasks.remove(removeIndex);
         if (removedTask != task) {
-            if (removedTask.hasChild()) {
+            if (removedTask.hasChild() && !removedTask.isActivityTypeHome()) {
                 Slog.i(TAG, "Add " + removedTask + " to hidden list because adding " + task);
                 // A non-empty task is replaced by a new task. Because the removed task is no longer
                 // managed by the recent tasks list, add it to the hidden list to prevent the task
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index db44532..fb592e1 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -481,8 +481,6 @@
     // to layout without loading all the task snapshots
     final PersistedTaskSnapshotData mLastTaskSnapshotData;
 
-    private final Rect mTmpDimBoundsRect = new Rect();
-
     /** @see #setCanAffectSystemUiFlags */
     private boolean mCanAffectSystemUiFlags = true;
 
@@ -3008,7 +3006,8 @@
 
     /** Checking if self or its child tasks are animated by recents animation. */
     boolean isAnimatingByRecents() {
-        return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS);
+        return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS)
+                || mTransitionController.isTransientHide(this);
     }
 
     WindowState getTopVisibleAppMainWindow() {
@@ -3254,22 +3253,24 @@
 
     @Override
     void prepareSurfaces() {
-        mDimmer.resetDimStates();
+        final Rect dimBounds = mDimmer.resetDimStates();
         super.prepareSurfaces();
-        getDimBounds(mTmpDimBoundsRect);
 
-        // Bounds need to be relative, as the dim layer is a child.
-        if (inFreeformWindowingMode()) {
-            getBounds(mTmpRect);
-            mTmpDimBoundsRect.offsetTo(mTmpDimBoundsRect.left - mTmpRect.left,
-                    mTmpDimBoundsRect.top - mTmpRect.top);
-        } else {
-            mTmpDimBoundsRect.offsetTo(0, 0);
+        if (dimBounds != null) {
+            getDimBounds(dimBounds);
+
+            // Bounds need to be relative, as the dim layer is a child.
+            if (inFreeformWindowingMode()) {
+                getBounds(mTmpRect);
+                dimBounds.offsetTo(dimBounds.left - mTmpRect.left, dimBounds.top - mTmpRect.top);
+            } else {
+                dimBounds.offsetTo(0, 0);
+            }
         }
 
         final SurfaceControl.Transaction t = getSyncTransaction();
 
-        if (mDimmer.updateDims(t, mTmpDimBoundsRect)) {
+        if (dimBounds != null && mDimmer.updateDims(t)) {
             scheduleAnimation();
         }
 
@@ -4687,7 +4688,7 @@
         if (!isAttached()) {
             return;
         }
-        mTransitionController.collect(this);
+        mTransitionController.recordTaskOrder(this);
 
         final TaskDisplayArea taskDisplayArea = getDisplayArea();
 
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 612fc4b..1d232fe 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2923,14 +2923,15 @@
             return;
         }
 
-        mDimmer.resetDimStates();
+        final Rect dimBounds = mDimmer.resetDimStates();
         super.prepareSurfaces();
 
-        // Bounds need to be relative, as the dim layer is a child.
-        final Rect dimBounds = getBounds();
-        dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */);
-        if (mDimmer.updateDims(getSyncTransaction(), dimBounds)) {
-            scheduleAnimation();
+        if (dimBounds != null) {
+            // Bounds need to be relative, as the dim layer is a child.
+            dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */);
+            if (mDimmer.updateDims(getSyncTransaction())) {
+                scheduleAnimation();
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 1fb3534..3a909ce 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -109,7 +109,7 @@
  */
 class Transition implements BLASTSyncEngine.TransactionReadyListener {
     private static final String TAG = "Transition";
-    private static final String TRACE_NAME_PLAY_TRANSITION = "PlayTransition";
+    private static final String TRACE_NAME_PLAY_TRANSITION = "playing";
 
     /** The default package for resources */
     private static final String DEFAULT_PACKAGE = "android";
@@ -511,8 +511,10 @@
         if (mParticipants.contains(wc)) return;
         // Wallpaper is like in a static drawn state unless display may have changes, so exclude
         // the case to reduce transition latency waiting for the unchanged wallpaper to redraw.
-        final boolean needSyncDraw = !isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent);
-        if (needSyncDraw) {
+        final boolean needSync = (!isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent))
+                // Transient-hide may be hidden later, so no need to request redraw.
+                && !isInTransientHide(wc);
+        if (needSync) {
             mSyncEngine.addToSyncSet(mSyncId, wc);
         }
         ChangeInfo info = mChanges.get(wc);
@@ -521,10 +523,7 @@
             mChanges.put(wc, info);
         }
         mParticipants.add(wc);
-        if (wc.getDisplayContent() != null && !mTargetDisplays.contains(wc.getDisplayContent())) {
-            mTargetDisplays.add(wc.getDisplayContent());
-            addOnTopTasks(wc.getDisplayContent(), mOnTopTasksStart);
-        }
+        recordDisplay(wc.getDisplayContent());
         if (info.mShowWallpaper) {
             // Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set.
             final WindowState wallpaper =
@@ -535,6 +534,20 @@
         }
     }
 
+    private void recordDisplay(DisplayContent dc) {
+        if (dc == null || mTargetDisplays.contains(dc)) return;
+        mTargetDisplays.add(dc);
+        addOnTopTasks(dc, mOnTopTasksStart);
+    }
+
+    /**
+     * Records information about the initial task order. This does NOT collect anything. Call this
+     * before any ordering changes *could* occur, but it is not known yet if it will occur.
+     */
+    void recordTaskOrder(WindowContainer from) {
+        recordDisplay(from.getDisplayContent());
+    }
+
     /** Adds the top non-alwaysOnTop tasks within `task` to `out`. */
     private static void addOnTopTasks(Task task, ArrayList<Task> out) {
         for (int i = task.getChildCount() - 1; i >= 0; --i) {
@@ -870,8 +883,7 @@
      */
     void finishTransition() {
         if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER) && mIsPlayerEnabled) {
-            Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
-                    System.identityHashCode(this));
+            asyncTraceEnd(System.identityHashCode(this));
         }
         mLogger.mFinishTimeNs = SystemClock.elapsedRealtimeNanos();
         mController.mLoggerHandler.post(mLogger::logOnFinish);
@@ -907,6 +919,8 @@
             final WindowContainer<?> participant = mParticipants.valueAt(i);
             final ActivityRecord ar = participant.asActivityRecord();
             if (ar != null) {
+                final Task task = ar.getTask();
+                if (task == null) continue;
                 boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar);
                 // We need both the expected visibility AND current requested-visibility to be
                 // false. If it is expected-visible but not currently visible, it means that
@@ -925,9 +939,7 @@
                     if (commitVisibility) {
                         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                                 "  Commit activity becoming invisible: %s", ar);
-                        final Task task = ar.getTask();
-                        if (task != null && !task.isVisibleRequested()
-                                && mTransientLaunches != null) {
+                        if (mTransientLaunches != null && !task.isVisibleRequested()) {
                             // If transition is transient, then snapshots are taken at end of
                             // transition.
                             mController.mSnapshotController.mTaskSnapshotController
@@ -952,8 +964,9 @@
 
                     // Since transient launches don't automatically take focus, make sure we
                     // synchronize focus since we committed to the launch.
-                    if (ar.isTopRunningActivity()) {
-                        ar.moveFocusableActivityToTop("transitionFinished");
+                    if (!task.isFocused() && ar.isTopRunningActivity()) {
+                        mController.mAtm.setLastResumedActivityUncheckLocked(ar,
+                                "transitionFinished");
                     }
                 }
                 continue;
@@ -1199,13 +1212,12 @@
         if (primaryDisplay.isKeyguardLocked()) {
             mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
         }
-        // Check whether the participants were animated from back navigation.
-        mController.mAtm.mBackNavigationController.onTransactionReady(this);
-
         collectOrderChanges();
 
         // Resolve the animating targets from the participants.
         mTargets = calculateTargets(mParticipants, mChanges);
+        // Check whether the participants were animated from back navigation.
+        mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets);
         final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
         info.setDebugId(mSyncId);
 
@@ -1322,8 +1334,7 @@
                 mController.getTransitionPlayer().onTransitionReady(
                         mToken, info, transaction, mFinishTransaction);
                 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
-                    Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
-                            System.identityHashCode(this));
+                    asyncTraceBegin(TRACE_NAME_PLAY_TRANSITION, System.identityHashCode(this));
                 }
             } catch (RemoteException e) {
                 // If there's an exception when trying to send the mergedTransaction to the
@@ -2310,6 +2321,14 @@
         return isCollecting() && mSyncId >= 0;
     }
 
+    static void asyncTraceBegin(@NonNull String name, int cookie) {
+        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, name, cookie);
+    }
+
+    static void asyncTraceEnd(int cookie) {
+        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie);
+    }
+
     @VisibleForTesting
     static class ChangeInfo {
         private static final int FLAG_NONE = 0;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index bcb8c46..3bf8969 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -37,7 +37,6 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
-import android.os.Trace;
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.TimeUtils;
@@ -334,28 +333,6 @@
         return inCollectingTransition(wc) || inPlayingTransition(wc);
     }
 
-    boolean inRecentsTransition(@NonNull WindowContainer wc) {
-        for (WindowContainer p = wc; p != null; p = p.getParent()) {
-            // TODO(b/221417431): replace this with deterministic snapshots
-            if (mCollectingTransition == null) break;
-            if ((mCollectingTransition.getFlags() & TRANSIT_FLAG_IS_RECENTS) != 0
-                    && mCollectingTransition.mParticipants.contains(wc)) {
-                return true;
-            }
-        }
-
-        for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
-            for (WindowContainer p = wc; p != null; p = p.getParent()) {
-                // TODO(b/221417431): replace this with deterministic snapshots
-                if ((mPlayingTransitions.get(i).getFlags() & TRANSIT_FLAG_IS_RECENTS) != 0
-                        && mPlayingTransitions.get(i).mParticipants.contains(p)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     /** @return {@code true} if wc is in a participant subtree */
     boolean isTransitionOnDisplay(@NonNull DisplayContent dc) {
         if (mCollectingTransition != null && mCollectingTransition.isOnDisplay(dc)) {
@@ -577,12 +554,16 @@
         return transition;
     }
 
-    /** Requests transition for a window container which will be removed or invisible. */
-    void requestCloseTransitionIfNeeded(@NonNull WindowContainer<?> wc) {
-        if (mTransitionPlayer == null) return;
+    /**
+     * Requests transition for a window container which will be removed or invisible.
+     * @return the new transition if it was created for this request, `null` otherwise.
+     */
+    Transition requestCloseTransitionIfNeeded(@NonNull WindowContainer<?> wc) {
+        if (mTransitionPlayer == null) return null;
+        Transition out = null;
         if (wc.isVisibleRequested()) {
             if (!isCollecting()) {
-                requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */),
+                out = requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */),
                         wc.asTask(), null /* remoteTransition */, null /* displayChange */);
             }
             collectExistenceChange(wc);
@@ -591,6 +572,7 @@
             // collecting, this should be a member just in case.
             collect(wc);
         }
+        return out;
     }
 
     /** @see Transition#collect */
@@ -605,6 +587,12 @@
         mCollectingTransition.collectExistenceChange(wc);
     }
 
+    /** @see Transition#recordTaskOrder */
+    void recordTaskOrder(@NonNull WindowContainer wc) {
+        if (mCollectingTransition == null) return;
+        mCollectingTransition.recordTaskOrder(wc);
+    }
+
     /**
      * Collects the window containers which need to be synced with the changing display area into
      * the current collecting transition.
@@ -762,12 +750,12 @@
             // happening in app), so pause task snapshot persisting to not increase the load.
             mAtm.mWindowManager.mSnapshotController.setPause(true);
             mAnimatingState = true;
-            Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "transitAnim", 0);
+            Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */);
         } else if (!animatingState && mAnimatingState) {
             t.setEarlyWakeupEnd();
             mAtm.mWindowManager.mSnapshotController.setPause(false);
             mAnimatingState = false;
-            Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "transitAnim", 0);
+            Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 4117641..cf6efd2 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -4047,7 +4047,7 @@
                 final Configuration mergedConfiguration =
                         configurationMerger != null
                                 ? configurationMerger.merge(mergedOverrideConfig,
-                                receiver.getConfiguration())
+                                        receiver.getRequestedOverrideConfiguration())
                                 : supplier.getConfiguration();
                 receiver.onRequestedOverrideConfigurationChanged(mergedConfiguration);
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index dc4f1ab..a11a7d8 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6673,9 +6673,8 @@
 
         mInputManagerCallback.dump(pw, "  ");
         mSnapshotController.dump(pw, " ");
-        if (mAccessibilityController.hasCallbacks()) {
-            mAccessibilityController.dump(pw, "  ");
-        }
+
+        dumpAccessibilityController(pw, /* force= */ false);
 
         if (dumpAll) {
             final WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
@@ -6712,6 +6711,23 @@
         }
     }
 
+    private void dumpAccessibilityController(PrintWriter pw, boolean force) {
+        boolean hasCallbacks = mAccessibilityController.hasCallbacks();
+        if (!hasCallbacks && !force) {
+            return;
+        }
+        if (!hasCallbacks) {
+            pw.println("AccessibilityController doesn't have callbacks, but printing it anways:");
+        } else {
+            pw.println("AccessibilityController:");
+        }
+        mAccessibilityController.dump(pw, "  ");
+    }
+
+    private void dumpAccessibilityLocked(PrintWriter pw) {
+        dumpAccessibilityController(pw, /* force= */ true);
+    }
+
     private boolean dumpWindows(PrintWriter pw, String name, boolean dumpAll) {
         final ArrayList<WindowState> windows = new ArrayList();
         if ("apps".equals(name) || "visible".equals(name) || "visible-apps".equals(name)) {
@@ -6831,6 +6847,7 @@
                 pw.println("    d[isplays]: active display contents");
                 pw.println("    t[okens]: token list");
                 pw.println("    w[indows]: window list");
+                pw.println("    a11y[accessibility]: accessibility-related state");
                 pw.println("    package-config: installed packages having app-specific config");
                 pw.println("    trace: print trace status and write Winscope trace to file");
                 pw.println("  cmd may also be a NAME to dump windows.  NAME may");
@@ -6894,6 +6911,11 @@
                     dumpWindowsLocked(pw, true, null);
                 }
                 return;
+            } else if ("accessibility".equals(cmd) || "a11y".equals(cmd)) {
+                synchronized (mGlobalLock) {
+                    dumpAccessibilityLocked(pw);
+                }
+                return;
             } else if ("all".equals(cmd)) {
                 synchronized (mGlobalLock) {
                     dumpWindowsLocked(pw, true, null);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 736f489..e5a49c3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5177,13 +5177,14 @@
     @Override
     void prepareSurfaces() {
         mIsDimming = false;
-        applyDims();
-        updateSurfacePositionNonOrganized();
-        // Send information to SurfaceFlinger about the priority of the current window.
-        updateFrameRateSelectionPriorityIfNeeded();
-        updateScaleIfNeeded();
-
-        mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
+        if (mHasSurface) {
+            applyDims();
+            updateSurfacePositionNonOrganized();
+            // Send information to SurfaceFlinger about the priority of the current window.
+            updateFrameRateSelectionPriorityIfNeeded();
+            updateScaleIfNeeded();
+            mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
+        }
         super.prepareSurfaces();
     }
 
@@ -5646,7 +5647,7 @@
 
     @Override
     boolean isSyncFinished() {
-        if (!isVisibleRequested()) {
+        if (!isVisibleRequested() || isFullyTransparent()) {
             // Don't wait for invisible windows. However, we don't alter the state in case the
             // window becomes visible while the sync group is still active.
             return true;
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 981844c..f96ca58 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -153,12 +153,6 @@
                 <xs:annotation name="nullable"/>
                 <xs:annotation name="final"/>
             </xs:element>
-            <!-- The highest (most severe) thermal status at which high-brightness-mode is allowed
-                 to operate. -->
-            <xs:element name="thermalStatusLimit" type="thermalStatus" minOccurs="0" maxOccurs="1">
-                <xs:annotation name="nonnull"/>
-                <xs:annotation name="final"/>
-            </xs:element>
             <xs:element name="allowInLowPowerMode" type="xs:boolean" minOccurs="0" maxOccurs="1">
                 <xs:annotation name="nonnull"/>
                 <xs:annotation name="final"/>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 8cb4837..ad6434e 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -156,7 +156,6 @@
     method @NonNull public final java.math.BigDecimal getMinimumLux_all();
     method @Nullable public final com.android.server.display.config.RefreshRateRange getRefreshRate_all();
     method @Nullable public final com.android.server.display.config.SdrHdrRatioMap getSdrHdrRatioMap_all();
-    method @NonNull public final com.android.server.display.config.ThermalStatus getThermalStatusLimit_all();
     method public com.android.server.display.config.HbmTiming getTiming_all();
     method @NonNull public final java.math.BigDecimal getTransitionPoint_all();
     method public final void setAllowInLowPowerMode_all(@NonNull boolean);
@@ -165,7 +164,6 @@
     method public final void setMinimumLux_all(@NonNull java.math.BigDecimal);
     method public final void setRefreshRate_all(@Nullable com.android.server.display.config.RefreshRateRange);
     method public final void setSdrHdrRatioMap_all(@Nullable com.android.server.display.config.SdrHdrRatioMap);
-    method public final void setThermalStatusLimit_all(@NonNull com.android.server.display.config.ThermalStatus);
     method public void setTiming_all(com.android.server.display.config.HbmTiming);
     method public final void setTransitionPoint_all(@NonNull java.math.BigDecimal);
   }
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index bbebbf2..19a0c5e 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -27,9 +27,10 @@
 import android.os.CancellationSignal;
 import android.os.RemoteException;
 import android.service.credentials.CallingAppInfo;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.ArrayList;
+import java.util.Set;
 
 /**
  * Central session for a single clearCredentialState request. This class listens to the
@@ -43,11 +44,12 @@
     public ClearRequestSession(Context context, RequestSession.SessionLifetime sessionCallback,
             Object lock, int userId, int callingUid,
             IClearCredentialStateCallback callback, ClearCredentialStateRequest request,
-            CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal,
+            CallingAppInfo callingAppInfo, Set<ComponentName> enabledProviders,
+            CancellationSignal cancellationSignal,
             long startedTimestamp) {
         super(context, sessionCallback, lock, userId, callingUid, request, callback,
                 RequestInfo.TYPE_UNDEFINED,
-                callingAppInfo, cancellationSignal, startedTimestamp);
+                callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp);
     }
 
     /**
@@ -65,7 +67,8 @@
                 .createNewSession(mContext, mUserId, providerInfo,
                         this, remoteCredentialService);
         if (providerClearSession != null) {
-            Log.i(TAG, "In startProviderSession - provider session created and being added");
+            Slog.d(TAG, "In startProviderSession - provider session created "
+                    + "and being added for: " + providerInfo.getComponentName());
             mProviders.put(providerClearSession.getComponentName().flattenToString(),
                     providerClearSession);
         }
@@ -75,12 +78,12 @@
     @Override // from provider session
     public void onProviderStatusChanged(ProviderSession.Status status,
             ComponentName componentName, ProviderSession.CredentialsSource source) {
-        Log.i(TAG, "in onStatusChanged with status: " + status);
+        Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source);
         if (ProviderSession.isTerminatingStatus(status)) {
-            Log.i(TAG, "in onStatusChanged terminating status");
+            Slog.d(TAG, "in onProviderStatusChanged terminating status");
             onProviderTerminated(componentName);
         } else if (ProviderSession.isCompletionStatus(status)) {
-            Log.i(TAG, "in onStatusChanged isCompletionStatus status");
+            Slog.d(TAG, "in onProviderStatusChanged isCompletionStatus status");
             onProviderResponseComplete(componentName);
         }
     }
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 4c456a8..a04143a 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -33,11 +33,12 @@
 import android.os.RemoteException;
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.PermissionUtils;
-import android.util.Log;
+import android.util.Slog;
 
 import com.android.server.credentials.metrics.ProviderStatusForMetrics;
 
 import java.util.ArrayList;
+import java.util.Set;
 
 /**
  * Central session for a single {@link CredentialManager#createCredential} request.
@@ -54,11 +55,12 @@
             CreateCredentialRequest request,
             ICreateCredentialCallback callback,
             CallingAppInfo callingAppInfo,
+            Set<ComponentName> enabledProviders,
             CancellationSignal cancellationSignal,
             long startedTimestamp) {
         super(context, sessionCallback, lock, userId, callingUid, request, callback,
                 RequestInfo.TYPE_CREATE,
-                callingAppInfo, cancellationSignal, startedTimestamp);
+                callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp);
     }
 
     /**
@@ -75,7 +77,8 @@
                 .createNewSession(mContext, mUserId, providerInfo,
                         this, remoteCredentialService);
         if (providerCreateSession != null) {
-            Log.i(TAG, "In startProviderSession - provider session created and being added");
+            Slog.d(TAG, "In initiateProviderSession - provider session created and "
+                    + "being added for: " + providerInfo.getComponentName());
             mProviders.put(providerCreateSession.getComponentName().flattenToString(),
                     providerCreateSession);
         }
@@ -118,7 +121,7 @@
     @Override
     public void onFinalResponseReceived(ComponentName componentName,
             @Nullable CreateCredentialResponse response) {
-        Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
+        Slog.d(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
         mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
         mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(mProviders.get(
                 componentName.flattenToString()).mProviderSessionMetric
@@ -161,13 +164,13 @@
     @Override
     public void onProviderStatusChanged(ProviderSession.Status status,
             ComponentName componentName, ProviderSession.CredentialsSource source) {
-        Log.i(TAG, "in onProviderStatusChanged with status: " + status);
+        Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source);
         // If all provider responses have been received, we can either need the UI,
         // or we need to respond with error. The only other case is the entry being
         // selected after the UI has been invoked which has a separate code path.
         if (!isAnyProviderPending()) {
             if (isUiInvocationNeeded()) {
-                Log.i(TAG, "in onProviderStatusChanged - isUiInvocationNeeded");
+                Slog.d(TAG, "in onProviderStatusChanged - isUiInvocationNeeded");
                 getProviderDataAndInitiateUi();
             } else {
                 respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 90f30b5..06b96eb 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -458,6 +458,7 @@
                             callback,
                             request,
                             constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
+                            getEnabledProviders(),
                             CancellationSignal.fromTransport(cancelTransport),
                             timestampBegan);
             addSessionLocked(userId, session);
@@ -512,6 +513,7 @@
                             getCredentialCallback,
                             request,
                             constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
+                            getEnabledProviders(),
                             CancellationSignal.fromTransport(cancelTransport),
                             timestampBegan,
                             prepareGetCredentialCallback);
@@ -629,6 +631,7 @@
                             request,
                             callback,
                             constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
+                            getEnabledProviders(),
                             CancellationSignal.fromTransport(cancelTransport),
                             timestampBegan);
             addSessionLocked(userId, session);
@@ -793,7 +796,7 @@
                 return DeviceConfig.getBoolean(
                         DeviceConfig.NAMESPACE_CREDENTIAL,
                         DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER,
-                        false);
+                        true);
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
@@ -846,6 +849,7 @@
                             callback,
                             request,
                             constructCallingAppInfo(callingPackage, userId, null),
+                            getEnabledProviders(),
                             CancellationSignal.fromTransport(cancelTransport),
                             timestampBegan);
             addSessionLocked(userId, session);
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index e16d48e..8750906 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -20,7 +20,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ServiceInfo;
 import android.credentials.CredentialManager;
 import android.credentials.CredentialProviderInfo;
 import android.credentials.ui.DisabledProviderData;
@@ -38,34 +37,32 @@
 import android.util.Slog;
 
 import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.UUID;
-import java.util.stream.Collectors;
 
 /** Initiates the Credential Manager UI and receives results. */
 public class CredentialManagerUi {
     private static final String TAG = "CredentialManagerUi";
     @NonNull
     private final CredentialManagerUiCallback mCallbacks;
-    @NonNull private final Context mContext;
+    @NonNull
+    private final Context mContext;
     // TODO : Use for starting the activity for this user
     private final int mUserId;
 
     private UiStatus mStatus;
 
-    /** Creates intent that is ot be invoked to cancel an in-progress UI session. */
-    public Intent createCancelIntent(IBinder requestId, String packageName) {
-        return IntentFactory.createCancelUiIntent(requestId, /*shouldShowCancellationUi=*/ true,
-                packageName);
-    }
+    private final Set<ComponentName> mEnabledProviders;
 
     enum UiStatus {
         IN_PROGRESS,
         USER_INTERACTION,
         NOT_STARTED, TERMINATED
     }
-    @NonNull private final ResultReceiver mResultReceiver = new ResultReceiver(
+
+    @NonNull
+    private final ResultReceiver mResultReceiver = new ResultReceiver(
             new Handler(Looper.getMainLooper())) {
         @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
@@ -105,24 +102,33 @@
         }
     }
 
+    /** Creates intent that is ot be invoked to cancel an in-progress UI session. */
+    public Intent createCancelIntent(IBinder requestId, String packageName) {
+        return IntentFactory.createCancelUiIntent(requestId, /*shouldShowCancellationUi=*/ true,
+                packageName);
+    }
+
     /**
      * Interface to be implemented by any class that wishes to get callbacks from the UI.
      */
     public interface CredentialManagerUiCallback {
         /** Called when the user makes a selection. */
         void onUiSelection(UserSelectionDialogResult selection);
+
         /** Called when the UI is canceled without a successful provider result. */
         void onUiCancellation(boolean isUserCancellation);
 
         /** Called when the selector UI fails to come up (mostly due to parsing issue today). */
         void onUiSelectorInvocationFailure();
     }
+
     public CredentialManagerUi(Context context, int userId,
-            CredentialManagerUiCallback callbacks) {
+            CredentialManagerUiCallback callbacks, Set<ComponentName> enabledProviders) {
         Log.i(TAG, "In CredentialManagerUi constructor");
         mContext = context;
         mUserId = userId;
         mCallbacks = callbacks;
+        mEnabledProviders = enabledProviders;
         mStatus = UiStatus.IN_PROGRESS;
     }
 
@@ -139,37 +145,28 @@
     /**
      * Creates a {@link PendingIntent} to be used to invoke the credential manager selector UI,
      * by the calling app process.
-     * @param requestInfo the information about the request
+     *
+     * @param requestInfo      the information about the request
      * @param providerDataList the list of provider data from remote providers
      */
     public PendingIntent createPendingIntent(
             RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
         Log.i(TAG, "In createPendingIntent");
 
-        ArrayList<DisabledProviderData> disabledProviderDataList = new ArrayList<>();
-        Set<String> enabledProviders = providerDataList.stream()
-                .map(ProviderData::getProviderFlattenedComponentName)
-                .collect(Collectors.toUnmodifiableSet());
-        Set<String> allProviders =
+        List<CredentialProviderInfo> allProviders =
                 CredentialProviderInfoFactory.getCredentialProviderServices(
-                                mContext,
-                                mUserId,
-                                CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY,
-                                new HashSet<>())
-                        .stream()
-                        .map(CredentialProviderInfo::getServiceInfo)
-                        .map(ServiceInfo::getComponentName)
-                        .map(ComponentName::flattenToString)
-                        .collect(Collectors.toUnmodifiableSet());
+                        mContext,
+                        mUserId,
+                        CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY,
+                        mEnabledProviders);
 
-        for (String provider: allProviders) {
-            if (!enabledProviders.contains(provider)) {
-                disabledProviderDataList.add(new DisabledProviderData(provider));
-            }
-        }
+        List<DisabledProviderData> disabledProviderDataList = allProviders.stream()
+                .filter(provider -> !provider.isEnabled())
+                .map(disabledProvider -> new DisabledProviderData(
+                        disabledProvider.getComponentName().flattenToString())).toList();
 
         Intent intent = IntentFactory.createCredentialSelectorIntent(requestInfo, providerDataList,
-                        disabledProviderDataList, mResultReceiver)
+                        new ArrayList<>(disabledProviderDataList), mResultReceiver)
                 .setAction(UUID.randomUUID().toString());
         //TODO: Create unique pending intent using request code and cancel any pre-existing pending
         // intents
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 2548bd8..208a4be 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -30,11 +30,12 @@
 import android.os.CancellationSignal;
 import android.os.RemoteException;
 import android.service.credentials.CallingAppInfo;
-import android.util.Log;
+import android.util.Slog;
 
 import com.android.server.credentials.metrics.ProviderStatusForMetrics;
 
 import java.util.ArrayList;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -45,22 +46,26 @@
         IGetCredentialCallback, GetCredentialResponse>
         implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
     private static final String TAG = "GetRequestSession";
+
     public GetRequestSession(Context context, RequestSession.SessionLifetime sessionCallback,
             Object lock, int userId, int callingUid,
             IGetCredentialCallback callback, GetCredentialRequest request,
-            CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal,
+            CallingAppInfo callingAppInfo, Set<ComponentName> enabledProviders,
+            CancellationSignal cancellationSignal,
             long startedTimestamp) {
         super(context, sessionCallback, lock, userId, callingUid, request, callback,
-                RequestInfo.TYPE_GET, callingAppInfo, cancellationSignal, startedTimestamp);
+                RequestInfo.TYPE_GET, callingAppInfo, enabledProviders, cancellationSignal,
+                startedTimestamp);
         int numTypes = (request.getCredentialOptions().stream()
                 .map(CredentialOption::getType).collect(
-                Collectors.toSet())).size(); // Dedupe type strings
+                        Collectors.toSet())).size(); // Dedupe type strings
         mRequestSessionMetric.collectGetFlowInitialMetricInfo(numTypes);
     }
 
     /**
      * Creates a new provider session, and adds it list of providers that are contributing to
      * this session.
+     *
      * @return the provider session created within this request session, for the given provider
      * info.
      */
@@ -72,7 +77,8 @@
                 .createNewSession(mContext, mUserId, providerInfo,
                         this, remoteCredentialService);
         if (providerGetSession != null) {
-            Log.i(TAG, "In startProviderSession - provider session created and being added");
+            Slog.d(TAG, "In startProviderSession - provider session created and "
+                    + "being added for: " + providerInfo.getComponentName());
             mProviders.put(providerGetSession.getComponentName().flattenToString(),
                     providerGetSession);
         }
@@ -111,7 +117,7 @@
     @Override
     public void onFinalResponseReceived(ComponentName componentName,
             @Nullable GetCredentialResponse response) {
-        Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
+        Slog.d(TAG, "onFinalResponseReceived from: " + componentName.flattenToString());
         mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
         mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(
                 mProviders.get(componentName.flattenToString())
@@ -149,13 +155,14 @@
     @Override
     public void onUiSelectorInvocationFailure() {
         respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
-                    "No credentials available.");
+                "No credentials available.");
     }
 
     @Override
     public void onProviderStatusChanged(ProviderSession.Status status,
             ComponentName componentName, ProviderSession.CredentialsSource source) {
-        Log.i(TAG, "in onStatusChanged with status: " + status + "and source: " + source);
+        Slog.d(TAG, "in onStatusChanged for: " + componentName + ", with status: "
+                + status + ", and source: " + source);
 
         // Auth entry was selected, and it did not have any underlying credentials
         if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) {
@@ -168,7 +175,7 @@
             // or we need to respond with error. The only other case is the entry being
             // selected after the UI has been invoked which has a separate code path.
             if (isUiInvocationNeeded()) {
-                Log.i(TAG, "in onProviderStatusChanged - isUiInvocationNeeded");
+                Slog.d(TAG, "in onProviderStatusChanged - isUiInvocationNeeded");
                 getProviderDataAndInitiateUi();
             } else {
                 respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index 88f3e6c..9e7a87e 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -33,7 +33,6 @@
 import android.os.RemoteException;
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.PermissionUtils;
-import android.util.Log;
 import android.util.Slog;
 
 import java.util.ArrayList;
@@ -52,10 +51,11 @@
     public PrepareGetRequestSession(Context context,
             RequestSession.SessionLifetime sessionCallback, Object lock, int userId,
             int callingUid, IGetCredentialCallback getCredCallback, GetCredentialRequest request,
-            CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal,
-            long startedTimestamp, IPrepareGetCredentialCallback prepareGetCredentialCallback) {
+            CallingAppInfo callingAppInfo, Set<ComponentName> enabledProviders,
+            CancellationSignal cancellationSignal, long startedTimestamp,
+            IPrepareGetCredentialCallback prepareGetCredentialCallback) {
         super(context, sessionCallback, lock, userId, callingUid, getCredCallback, request,
-                callingAppInfo, cancellationSignal, startedTimestamp);
+                callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp);
         int numTypes = (request.getCredentialOptions().stream()
                 .map(CredentialOption::getType).collect(
                         Collectors.toSet())).size(); // Dedupe type strings
@@ -66,6 +66,9 @@
     @Override
     public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName,
             ProviderSession.CredentialsSource source) {
+        Slog.d(TAG, "in onProviderStatusChanged with status: " + status + ", and "
+                + "source: " + source);
+
         switch (source) {
             case REMOTE_PROVIDER:
                 // Remote provider's status changed. We should check if all providers are done, and
@@ -122,7 +125,7 @@
                             hasPermission,
                             credentialTypes, hasAuthenticationResults, hasRemoteResults, uiIntent));
         } catch (RemoteException e) {
-            Log.e(TAG, "EXCEPTION while mPendingCallback.onResponse", e);
+            Slog.e(TAG, "EXCEPTION while mPendingCallback.onResponse", e);
         }
     }
 
@@ -137,7 +140,7 @@
                             /*hasRemoteResults=*/ false,
                             /*pendingIntent=*/ null));
         } catch (RemoteException e) {
-            Log.e(TAG, "EXCEPTION while mPendingCallback.onResponse", e);
+            Slog.e(TAG, "EXCEPTION while mPendingCallback.onResponse", e);
         }
     }
 
@@ -178,10 +181,8 @@
     private PendingIntent getUiIntent() {
         ArrayList<ProviderData> providerDataList = new ArrayList<>();
         for (ProviderSession session : mProviders.values()) {
-            Log.i(TAG, "preparing data for : " + session.getComponentName());
             ProviderData providerData = session.prepareUiData();
             if (providerData != null) {
-                Log.i(TAG, "Provider data is not null");
                 providerDataList.add(providerData);
             }
         }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index eaf58f1..9ec0ecd 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -23,9 +23,9 @@
 import android.credentials.CredentialProviderInfo;
 import android.credentials.ui.ProviderData;
 import android.credentials.ui.ProviderPendingIntentResponse;
+import android.os.ICancellationSignal;
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.ClearCredentialStateRequest;
-import android.util.Log;
 import android.util.Slog;
 
 /**
@@ -80,7 +80,7 @@
 
     @Override
     public void onProviderResponseSuccess(@Nullable Void response) {
-        Log.i(TAG, "in onProviderResponseSuccess");
+        Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
         mProviderResponseSet = true;
         updateStatusAndInvokeCallback(Status.COMPLETE,
                 /*source=*/ CredentialsSource.REMOTE_PROVIDER);
@@ -104,11 +104,16 @@
             updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
                     /*source=*/ CredentialsSource.REMOTE_PROVIDER);
         } else {
-            Slog.i(TAG, "Component names different in onProviderServiceDied - "
+            Slog.w(TAG, "Component names different in onProviderServiceDied - "
                     + "this should not happen");
         }
     }
 
+    @Override
+    public void onProviderCancellable(ICancellationSignal cancellation) {
+        mProviderCancellationSignal = cancellation;
+    }
+
     @Nullable
     @Override
     protected ProviderData prepareUiData() {
@@ -126,8 +131,7 @@
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
             startCandidateMetrics();
-            mProviderCancellationSignal =
-                    mRemoteCredentialService.onClearCredentialState(mProviderRequest, this);
+            mRemoteCredentialService.onClearCredentialState(mProviderRequest, this);
         }
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index c657e3b..09433db 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -29,6 +29,7 @@
 import android.credentials.ui.Entry;
 import android.credentials.ui.ProviderPendingIntentResponse;
 import android.os.Bundle;
+import android.os.ICancellationSignal;
 import android.service.credentials.BeginCreateCredentialRequest;
 import android.service.credentials.BeginCreateCredentialResponse;
 import android.service.credentials.CallingAppInfo;
@@ -36,7 +37,6 @@
 import android.service.credentials.CreateEntry;
 import android.service.credentials.CredentialProviderService;
 import android.service.credentials.RemoteEntry;
-import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 
@@ -92,7 +92,8 @@
                     createRequestSession.mHybridService
             );
         }
-        Log.i(TAG, "Unable to create provider session");
+        Slog.d(TAG, "Unable to create provider session for: "
+                + providerInfo.getComponentName());
         return null;
     }
 
@@ -121,7 +122,6 @@
             return new CreateCredentialRequest(callingAppInfo, capability,
                     clientRequest.getCredentialData());
         }
-        Log.i(TAG, "Unable to create provider request - capabilities do not match");
         return null;
     }
 
@@ -145,7 +145,7 @@
     @Override
     public void onProviderResponseSuccess(
             @Nullable BeginCreateCredentialResponse response) {
-        Log.i(TAG, "in onProviderResponseSuccess");
+        Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
         onSetInitialRemoteResponse(response);
     }
 
@@ -168,13 +168,17 @@
             updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
                     /*source=*/ CredentialsSource.REMOTE_PROVIDER);
         } else {
-            Slog.i(TAG, "Component names different in onProviderServiceDied - "
+            Slog.w(TAG, "Component names different in onProviderServiceDied - "
                     + "this should not happen");
         }
     }
 
+    @Override
+    public void onProviderCancellable(ICancellationSignal cancellation) {
+        mProviderCancellationSignal = cancellation;
+    }
+
     private void onSetInitialRemoteResponse(BeginCreateCredentialResponse response) {
-        Log.i(TAG, "onSetInitialRemoteResponse with save entries");
         mProviderResponse = response;
         mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(),
                 response.getRemoteCreateEntry());
@@ -193,14 +197,12 @@
     @Nullable
     protected CreateCredentialProviderData prepareUiData()
             throws IllegalArgumentException {
-        Log.i(TAG, "In prepareUiData");
         if (!ProviderSession.isUiInvokingStatus(getStatus())) {
-            Log.i(TAG, "In prepareUiData not in uiInvokingStatus");
+            Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString());
             return null;
         }
 
         if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) {
-            Log.i(TAG, "In prepareUiData save entries not null");
             return mProviderResponseDataHandler.toCreateCredentialProviderData();
         }
         return null;
@@ -212,7 +214,7 @@
         switch (entryType) {
             case SAVE_ENTRY_KEY:
                 if (mProviderResponseDataHandler.getCreateEntry(entryKey) == null) {
-                    Log.i(TAG, "Unexpected save entry key");
+                    Slog.w(TAG, "Unexpected save entry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
@@ -220,14 +222,14 @@
                 break;
             case REMOTE_ENTRY_KEY:
                 if (mProviderResponseDataHandler.getRemoteEntry(entryKey) == null) {
-                    Log.i(TAG, "Unexpected remote entry key");
+                    Slog.w(TAG, "Unexpected remote entry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
                 onRemoteEntrySelected(providerPendingIntentResponse);
                 break;
             default:
-                Log.i(TAG, "Unsupported entry type selected");
+                Slog.w(TAG, "Unsupported entry type selected");
                 invokeCallbackOnInternalInvalidState();
         }
     }
@@ -236,8 +238,7 @@
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
             startCandidateMetrics();
-            mProviderCancellationSignal =
-                    mRemoteCredentialService.onCreateCredential(mProviderRequest, this);
+            mRemoteCredentialService.onBeginCreateCredential(mProviderRequest, this);
         }
     }
 
@@ -263,7 +264,7 @@
         if (credentialResponse != null) {
             mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse);
         } else {
-            Log.i(TAG, "onSaveEntrySelected - no response or error found in pending "
+            Slog.w(TAG, "onSaveEntrySelected - no response or error found in pending "
                     + "intent response");
             invokeCallbackOnInternalInvalidState();
         }
@@ -279,14 +280,14 @@
     private CreateCredentialException maybeGetPendingIntentException(
             ProviderPendingIntentResponse pendingIntentResponse) {
         if (pendingIntentResponse == null) {
-            Log.i(TAG, "pendingIntentResponse is null");
+            Slog.w(TAG, "pendingIntentResponse is null");
             return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREATE_OPTIONS);
         }
         if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
             CreateCredentialException exception = PendingIntentResultHandler
                     .extractCreateCredentialException(pendingIntentResponse.getResultData());
             if (exception != null) {
-                Log.i(TAG, "Pending intent contains provider exception");
+                Slog.d(TAG, "Pending intent contains provider exception");
                 return exception;
             }
         } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
@@ -338,7 +339,7 @@
 
         public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) {
             if (!enforceRemoteEntryRestrictions(mExpectedRemoteEntryProviderService)) {
-                Log.i(TAG, "Remote entry being dropped as it does not meet the restriction"
+                Slog.w(TAG, "Remote entry being dropped as it does not meet the restriction"
                         + "checks.");
                 return;
             }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 9c9c0c2..0c2b563 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -30,6 +30,7 @@
 import android.credentials.ui.Entry;
 import android.credentials.ui.GetCredentialProviderData;
 import android.credentials.ui.ProviderPendingIntentResponse;
+import android.os.ICancellationSignal;
 import android.service.credentials.Action;
 import android.service.credentials.BeginGetCredentialOption;
 import android.service.credentials.BeginGetCredentialRequest;
@@ -114,7 +115,8 @@
                     getRequestSession.mHybridService
             );
         }
-        Log.i(TAG, "Unable to create provider session");
+        Slog.d(TAG, "Unable to create provider session for: "
+                + providerInfo.getComponentName());
         return null;
     }
 
@@ -145,17 +147,15 @@
             android.credentials.GetCredentialRequest clientRequest,
             CredentialProviderInfo info
     ) {
+        Slog.d(TAG, "Filtering request options for: " + info.getComponentName());
         List<CredentialOption> filteredOptions = new ArrayList<>();
         for (CredentialOption option : clientRequest.getCredentialOptions()) {
             if (providerCapabilities.contains(option.getType())
                     && isProviderAllowed(option, info.getComponentName())
                     && checkSystemProviderRequirement(option, info.isSystemProvider())) {
-                Log.i(TAG, "In createProviderRequest - capability found : "
-                        + option.getType());
+                Slog.d(TAG, "Option of type: " + option.getType() + " meets all filtering"
+                        + "conditions");
                 filteredOptions.add(option);
-            } else {
-                Log.i(TAG, "In createProviderRequest - capability not "
-                        + "found, or provider not allowed : " + option.getType());
             }
         }
         if (!filteredOptions.isEmpty()) {
@@ -164,15 +164,14 @@
                     .setCredentialOptions(
                             filteredOptions).build();
         }
-        Log.i(TAG, "In createProviderRequest - returning null");
+        Slog.d(TAG, "No options filtered");
         return null;
     }
 
     private static boolean isProviderAllowed(CredentialOption option, ComponentName componentName) {
         if (!option.getAllowedProviders().isEmpty() && !option.getAllowedProviders().contains(
                 componentName)) {
-            Log.d(TAG, "Provider allow list specified but does not contain this provider: "
-                    + componentName.flattenToString());
+            Slog.d(TAG, "Provider allow list specified but does not contain this provider");
             return false;
         }
         return true;
@@ -181,7 +180,7 @@
     private static boolean checkSystemProviderRequirement(CredentialOption option,
             boolean isSystemProvider) {
         if (option.isSystemProviderRequired() && !isSystemProvider) {
-            Log.d(TAG, "System provider required, but this service is not a system provider");
+            Slog.d(TAG, "System provider required, but this service is not a system provider");
             return false;
         }
         return true;
@@ -209,6 +208,7 @@
     /** Called when the provider response has been updated by an external source. */
     @Override // Callback from the remote provider
     public void onProviderResponseSuccess(@Nullable BeginGetCredentialResponse response) {
+        Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
         onSetInitialRemoteResponse(response);
     }
 
@@ -230,21 +230,27 @@
             updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
                     /*source=*/ CredentialsSource.REMOTE_PROVIDER);
         } else {
-            Slog.i(TAG, "Component names different in onProviderServiceDied - "
+            Slog.w(TAG, "Component names different in onProviderServiceDied - "
                     + "this should not happen");
         }
     }
 
+    @Override
+    public void onProviderCancellable(ICancellationSignal cancellation) {
+        mProviderCancellationSignal = cancellation;
+    }
+
     @Override // Selection call from the request provider
     protected void onUiEntrySelected(String entryType, String entryKey,
             ProviderPendingIntentResponse providerPendingIntentResponse) {
-        Log.i(TAG, "onUiEntrySelected with entryKey: " + entryKey);
+        Slog.d(TAG, "onUiEntrySelected with entryType: " + entryType + ", and entryKey: "
+                + entryKey);
         switch (entryType) {
             case CREDENTIAL_ENTRY_KEY:
                 CredentialEntry credentialEntry = mProviderResponseDataHandler
                         .getCredentialEntry(entryKey);
                 if (credentialEntry == null) {
-                    Log.i(TAG, "Unexpected credential entry key");
+                    Slog.w(TAG, "Unexpected credential entry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
@@ -253,7 +259,7 @@
             case ACTION_ENTRY_KEY:
                 Action actionEntry = mProviderResponseDataHandler.getActionEntry(entryKey);
                 if (actionEntry == null) {
-                    Log.i(TAG, "Unexpected action entry key");
+                    Slog.w(TAG, "Unexpected action entry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
@@ -263,21 +269,21 @@
                 Action authenticationEntry = mProviderResponseDataHandler
                         .getAuthenticationAction(entryKey);
                 if (authenticationEntry == null) {
-                    Log.i(TAG, "Unexpected authenticationEntry key");
+                    Slog.w(TAG, "Unexpected authenticationEntry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
                 boolean additionalContentReceived =
                         onAuthenticationEntrySelected(providerPendingIntentResponse);
                 if (additionalContentReceived) {
-                    Log.i(TAG, "Additional content received - removing authentication entry");
+                    Slog.d(TAG, "Additional content received - removing authentication entry");
                     mProviderResponseDataHandler.removeAuthenticationAction(entryKey);
                     if (!mProviderResponseDataHandler.isEmptyResponse()) {
                         updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
                                 /*source=*/ CredentialsSource.AUTH_ENTRY);
                     }
                 } else {
-                    Log.i(TAG, "Additional content not received");
+                    Slog.d(TAG, "Additional content not received from authentication entry");
                     mProviderResponseDataHandler
                             .updateAuthEntryWithNoCredentialsReceived(entryKey);
                     updateStatusAndInvokeCallback(Status.NO_CREDENTIALS_FROM_AUTH_ENTRY,
@@ -288,12 +294,12 @@
                 if (mProviderResponseDataHandler.getRemoteEntry(entryKey) != null) {
                     onRemoteEntrySelected(providerPendingIntentResponse);
                 } else {
-                    Log.i(TAG, "Unexpected remote entry key");
+                    Slog.d(TAG, "Unexpected remote entry key");
                     invokeCallbackOnInternalInvalidState();
                 }
                 break;
             default:
-                Log.i(TAG, "Unsupported entry type selected");
+                Slog.w(TAG, "Unsupported entry type selected");
                 invokeCallbackOnInternalInvalidState();
         }
     }
@@ -302,9 +308,7 @@
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
             startCandidateMetrics();
-            mProviderCancellationSignal =
-                    mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this);
-            boolean foundSig = mProviderCancellationSignal == null;
+            mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this);
         }
     }
 
@@ -316,26 +320,24 @@
     @Override // Call from request session to data to be shown on the UI
     @Nullable
     protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
-        Log.i(TAG, "In prepareUiData");
         if (!ProviderSession.isUiInvokingStatus(getStatus())) {
-            Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
-                    + mComponentName.flattenToString());
+            Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString());
             return null;
         }
         if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) {
             return mProviderResponseDataHandler.toGetCredentialProviderData();
         }
-        Log.i(TAG, "In prepareUiData response null");
+        Slog.d(TAG, "In prepareUiData response null");
         return null;
     }
 
-    private Intent setUpFillInIntent(@NonNull String id) {
+    private Intent setUpFillInIntentWithFinalRequest(@NonNull String id) {
         // TODO: Determine if we should skip this entry if entry id is not set, or is set
         // but does not resolve to a valid option. For now, not skipping it because
         // it may be possible that the provider adds their own extras and expects to receive
         // those and complete the flow.
         if (mBeginGetOptionToCredentialOptionMap.get(id) == null) {
-            Log.i(TAG, "Id from Credential Entry does not resolve to a valid option");
+            Slog.w(TAG, "Id from Credential Entry does not resolve to a valid option");
             return new Intent();
         }
         return new Intent().putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_REQUEST,
@@ -378,7 +380,8 @@
                     getCredentialResponse);
             return;
         }
-        Log.i(TAG, "Pending intent response contains no credential, or error");
+        Slog.d(TAG, "Pending intent response contains no credential, or error "
+                + "for a credential entry");
         invokeCallbackOnInternalInvalidState();
     }
 
@@ -386,14 +389,12 @@
     private GetCredentialException maybeGetPendingIntentException(
             ProviderPendingIntentResponse pendingIntentResponse) {
         if (pendingIntentResponse == null) {
-            Log.i(TAG, "pendingIntentResponse is null");
             return null;
         }
         if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
             GetCredentialException exception = PendingIntentResultHandler
                     .extractGetCredentialException(pendingIntentResponse.getResultData());
             if (exception != null) {
-                Log.i(TAG, "Pending intent contains provider exception");
                 return exception;
             }
         } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
@@ -459,7 +460,7 @@
     /** Returns true if either an exception or a response is found. */
     private void onActionEntrySelected(ProviderPendingIntentResponse
             providerPendingIntentResponse) {
-        Log.i(TAG, "onActionEntrySelected");
+        Slog.d(TAG, "onActionEntrySelected");
         onCredentialEntrySelected(providerPendingIntentResponse);
     }
 
@@ -555,7 +556,8 @@
             String id = generateUniqueId();
             Entry entry = new Entry(CREDENTIAL_ENTRY_KEY,
                     id, credentialEntry.getSlice(),
-                    setUpFillInIntent(credentialEntry.getBeginGetCredentialOptionId()));
+                    setUpFillInIntentWithFinalRequest(credentialEntry
+                            .getBeginGetCredentialOptionId()));
             mUiCredentialEntries.put(id, new Pair<>(credentialEntry, entry));
             mCredentialEntryTypes.add(credentialEntry.getType());
         }
@@ -570,9 +572,7 @@
 
         public void addAuthenticationAction(Action authenticationAction,
                 @AuthenticationEntry.Status int status) {
-            Log.i(TAG, "In addAuthenticationAction");
             String id = generateUniqueId();
-            Log.i(TAG, "In addAuthenticationAction, id : " + id);
             AuthenticationEntry entry = new AuthenticationEntry(
                     AUTHENTICATION_ACTION_ENTRY_KEY,
                     id, authenticationAction.getSlice(),
@@ -587,7 +587,7 @@
 
         public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) {
             if (!enforceRemoteEntryRestrictions(mExpectedRemoteEntryProviderService)) {
-                Log.i(TAG, "Remote entry being dropped as it does not meet the restriction"
+                Slog.w(TAG, "Remote entry being dropped as it does not meet the restriction"
                         + " checks.");
                 return;
             }
@@ -711,7 +711,6 @@
                             == AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT)
                     .findFirst();
             if (previousMostRecentAuthEntry.isEmpty()) {
-                Log.i(TAG, "In updatePreviousMostRecentAuthEntry - previous entry not found");
                 return;
             }
             String id = previousMostRecentAuthEntry.get().getKey();
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index 9cf2721..c10f564 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -29,10 +29,11 @@
 import android.credentials.ui.GetCredentialProviderData;
 import android.credentials.ui.ProviderData;
 import android.credentials.ui.ProviderPendingIntentResponse;
+import android.os.ICancellationSignal;
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.CredentialEntry;
 import android.service.credentials.CredentialProviderService;
-import android.telecom.Log;
+import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -115,7 +116,7 @@
             @NonNull String servicePackageName,
             @NonNull CredentialOption requestOption) {
         super(context, requestOption, session,
-                new ComponentName(servicePackageName, servicePackageName) ,
+                new ComponentName(servicePackageName, servicePackageName),
                 userId, null);
         mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId);
         mCallingAppInfo = callingAppInfo;
@@ -132,7 +133,7 @@
             @NonNull String servicePackageName,
             @NonNull CredentialOption requestOption) {
         super(context, requestOption, session,
-                new ComponentName(servicePackageName, servicePackageName) ,
+                new ComponentName(servicePackageName, servicePackageName),
                 userId, null);
         mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId);
         mCallingAppInfo = callingAppInfo;
@@ -144,14 +145,12 @@
 
     private List<Entry> prepareUiCredentialEntries(
             @NonNull List<CredentialEntry> credentialEntries) {
-        Log.i(TAG, "in prepareUiProviderDataWithCredentials");
         List<Entry> credentialUiEntries = new ArrayList<>();
 
         // Populate the credential entries
         for (CredentialEntry credentialEntry : credentialEntries) {
             String entryId = generateUniqueId();
             mUiCredentialEntries.put(entryId, credentialEntry);
-            Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
             credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
                     credentialEntry.getSlice(),
                     setUpFillInIntent()));
@@ -171,15 +170,13 @@
 
     @Override
     protected ProviderData prepareUiData() {
-        Log.i(TAG, "In prepareUiData");
         if (!ProviderSession.isUiInvokingStatus(getStatus())) {
-            Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
-                    + mComponentName.flattenToString());
+            Slog.d(TAG, "No date for UI coming from: " + mComponentName.flattenToString());
             return null;
         }
         if (mProviderResponse == null) {
-            Log.i(TAG, "In prepareUiData response null");
-            throw new IllegalStateException("Response must be in completion mode");
+            Slog.w(TAG, "In prepareUiData but response is null. This is strange.");
+            return null;
         }
         return new GetCredentialProviderData.Builder(
                 mComponentName.flattenToString()).setActionChips(null)
@@ -199,13 +196,13 @@
             case CREDENTIAL_ENTRY_KEY:
                 CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey);
                 if (credentialEntry == null) {
-                    Log.i(TAG, "Unexpected credential entry key");
+                    Slog.w(TAG, "Unexpected credential entry key");
                     return;
                 }
                 onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse);
                 break;
             default:
-                Log.i(TAG, "Unsupported entry type selected");
+                Slog.w(TAG, "Unsupported entry type selected");
         }
     }
 
@@ -232,10 +229,8 @@
                 }
                 return;
             }
-
-            Log.i(TAG, "Pending intent response contains no credential, or error");
         }
-        Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result");
+        Slog.w(TAG, "CredentialEntry does not have a credential or a pending intent result");
     }
 
     @Override
@@ -255,13 +250,18 @@
     }
 
     @Override
+    public void onProviderCancellable(ICancellationSignal cancellation) {
+        // No need to do anything since this class does not rely on a remote service.
+    }
+
+    @Override
     protected void invokeSession() {
         mProviderResponse = mCredentialDescriptionRegistry
                 .getFilteredResultForProvider(mCredentialProviderPackageName,
                         mElementKeys);
         mCredentialEntries = mProviderResponse.stream().flatMap(
-                        (Function<CredentialDescriptionRegistry.FilterResult,
-                                Stream<CredentialEntry>>) filterResult
+                (Function<CredentialDescriptionRegistry.FilterResult,
+                        Stream<CredentialEntry>>) filterResult
                         -> filterResult.mCredentialEntries.stream())
                 .collect(Collectors.toList());
         updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
@@ -273,14 +273,13 @@
     protected GetCredentialException maybeGetPendingIntentException(
             ProviderPendingIntentResponse pendingIntentResponse) {
         if (pendingIntentResponse == null) {
-            android.util.Log.i(TAG, "pendingIntentResponse is null");
             return null;
         }
         if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
             GetCredentialException exception = PendingIntentResultHandler
                     .extractGetCredentialException(pendingIntentResponse.getResultData());
             if (exception != null) {
-                android.util.Log.i(TAG, "Pending intent contains provider exception");
+                Slog.d(TAG, "Pending intent contains provider exception");
                 return exception;
             }
         } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index d165756..d02a8c1 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -29,7 +29,6 @@
 import android.credentials.ui.ProviderPendingIntentResponse;
 import android.os.ICancellationSignal;
 import android.os.RemoteException;
-import android.util.Log;
 import android.util.Slog;
 
 import com.android.server.credentials.metrics.ProviderSessionMetric;
@@ -253,7 +252,7 @@
             @Nullable ComponentName expectedRemoteEntryProviderService) {
         // Check if the service is the one set by the OEM. If not silently reject this entry
         if (!mComponentName.equals(expectedRemoteEntryProviderService)) {
-            Log.i(TAG, "Remote entry being dropped as it is not from the service "
+            Slog.w(TAG, "Remote entry being dropped as it is not from the service "
                     + "configured by the OEM.");
             return false;
         }
@@ -270,15 +269,12 @@
                 return true;
             }
         } catch (SecurityException e) {
-            Log.i(TAG, "Error getting info for "
-                    + mComponentName.flattenToString() + ": " + e.getMessage());
+            Slog.e(TAG, "Error getting info for " + mComponentName.flattenToString(), e);
             return false;
         } catch (PackageManager.NameNotFoundException e) {
-            Log.i(TAG, "Error getting info for "
-                    + mComponentName.flattenToString() + ": " + e.getMessage());
+            Slog.i(TAG, "Error getting info for " + mComponentName.flattenToString(), e);
             return false;
         }
-        Log.i(TAG, "In enforceRemoteEntryRestrictions - remote entry checks fail");
         return false;
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index ff4e3b6..c1e9bc6 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -82,6 +82,9 @@
 
         /** Called when the remote provider service dies. */
         void onProviderServiceDied(RemoteCredentialService service);
+
+        /** Called to set the cancellation transport from the remote provider service. */
+        void onProviderCancellable(ICancellationSignal cancellation);
     }
 
     public RemoteCredentialService(@NonNull Context context,
@@ -117,43 +120,56 @@
      * @param callback the callback to be used to send back the provider response to the
      *                 {@link ProviderGetSession} class that maintains provider state
      */
-    public ICancellationSignal onBeginGetCredential(@NonNull BeginGetCredentialRequest request,
+    public void onBeginGetCredential(@NonNull BeginGetCredentialRequest request,
             ProviderCallbacks<BeginGetCredentialResponse> callback) {
         Log.i(TAG, "In onGetCredentials in RemoteCredentialService");
         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+        AtomicReference<CompletableFuture<BeginGetCredentialResponse>> futureRef =
+                new AtomicReference<>();
+
 
         CompletableFuture<BeginGetCredentialResponse> connectThenExecute = postAsync(service -> {
             CompletableFuture<BeginGetCredentialResponse> getCredentials =
                     new CompletableFuture<>();
             final long originalCallingUidToken = Binder.clearCallingIdentity();
             try {
-                ICancellationSignal cancellationSignal =
-                        service.onBeginGetCredential(request,
-                                new IBeginGetCredentialCallback.Stub() {
-                                    @Override
-                                    public void onSuccess(BeginGetCredentialResponse response) {
-                                        getCredentials.complete(response);
-                                    }
+                service.onBeginGetCredential(request,
+                        new IBeginGetCredentialCallback.Stub() {
+                            @Override
+                            public void onSuccess(BeginGetCredentialResponse response) {
+                                getCredentials.complete(response);
+                            }
 
-                                    @Override
-                                    public void onFailure(String errorType, CharSequence message) {
-                                        Log.i(TAG, "In onFailure in RemoteCredentialService");
-                                        String errorMsg = message == null ? "" : String.valueOf(
-                                                message);
-                                        getCredentials.completeExceptionally(
-                                                new GetCredentialException(errorType, errorMsg));
-                                    }
-                                });
-                cancellationSink.set(cancellationSignal);
+                            @Override
+                            public void onFailure(String errorType, CharSequence message) {
+                                Log.i(TAG, "In onFailure in RemoteCredentialService");
+                                String errorMsg = message == null ? "" : String.valueOf(
+                                        message);
+                                getCredentials.completeExceptionally(
+                                        new GetCredentialException(errorType, errorMsg));
+                            }
+
+                            @Override
+                            public void onCancellable(ICancellationSignal cancellation) {
+                                CompletableFuture<BeginGetCredentialResponse> future =
+                                        futureRef.get();
+                                if (future != null && future.isCancelled()) {
+                                    dispatchCancellationSignal(cancellation);
+                                } else {
+                                    cancellationSink.set(cancellation);
+                                    callback.onProviderCancellable(cancellation);
+                                }
+                            }
+                        });
                 return getCredentials;
             } finally {
                 Binder.restoreCallingIdentity(originalCallingUidToken);
             }
         }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+        futureRef.set(connectThenExecute);
 
         connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
                 handleExecutionResponse(result, error, cancellationSink, callback)));
-        return cancellationSink.get();
     }
 
     /**
@@ -164,10 +180,12 @@
      * @param callback the callback to be used to send back the provider response to the
      *                 {@link ProviderCreateSession} class that maintains provider state
      */
-    public ICancellationSignal onCreateCredential(@NonNull BeginCreateCredentialRequest request,
+    public void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request,
             ProviderCallbacks<BeginCreateCredentialResponse> callback) {
         Log.i(TAG, "In onCreateCredential in RemoteCredentialService");
         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+        AtomicReference<CompletableFuture<BeginCreateCredentialResponse>> futureRef =
+                new AtomicReference<>();
 
         CompletableFuture<BeginCreateCredentialResponse> connectThenExecute =
                 postAsync(service -> {
@@ -175,7 +193,7 @@
                             new CompletableFuture<>();
                     final long originalCallingUidToken = Binder.clearCallingIdentity();
                     try {
-                        ICancellationSignal cancellationSignal = service.onBeginCreateCredential(
+                        service.onBeginCreateCredential(
                                 request, new IBeginCreateCredentialCallback.Stub() {
                                     @Override
                                     public void onSuccess(BeginCreateCredentialResponse response) {
@@ -192,18 +210,28 @@
                                         createCredentialFuture.completeExceptionally(
                                                 new CreateCredentialException(errorType, errorMsg));
                                     }
+
+                                    @Override
+                                    public void onCancellable(ICancellationSignal cancellation) {
+                                        CompletableFuture<BeginCreateCredentialResponse> future =
+                                                futureRef.get();
+                                        if (future != null && future.isCancelled()) {
+                                            dispatchCancellationSignal(cancellation);
+                                        } else {
+                                            cancellationSink.set(cancellation);
+                                            callback.onProviderCancellable(cancellation);
+                                        }
+                                    }
                                 });
-                        cancellationSink.set(cancellationSignal);
                         return createCredentialFuture;
                     } finally {
                         Binder.restoreCallingIdentity(originalCallingUidToken);
                     }
                 }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+        futureRef.set(connectThenExecute);
 
         connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
                 handleExecutionResponse(result, error, cancellationSink, callback)));
-
-        return cancellationSink.get();
     }
 
     /**
@@ -214,10 +242,11 @@
      * @param callback the callback to be used to send back the provider response to the
      *                 {@link ProviderClearSession} class that maintains provider state
      */
-    public ICancellationSignal onClearCredentialState(@NonNull ClearCredentialStateRequest request,
+    public void onClearCredentialState(@NonNull ClearCredentialStateRequest request,
             ProviderCallbacks<Void> callback) {
         Log.i(TAG, "In onClearCredentialState in RemoteCredentialService");
         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+        AtomicReference<CompletableFuture<Void>> futureRef = new AtomicReference<>();
 
         CompletableFuture<Void> connectThenExecute =
                 postAsync(service -> {
@@ -225,7 +254,7 @@
                             new CompletableFuture<>();
                     final long originalCallingUidToken = Binder.clearCallingIdentity();
                     try {
-                        ICancellationSignal cancellationSignal = service.onClearCredentialState(
+                        service.onClearCredentialState(
                                 request, new IClearCredentialStateCallback.Stub() {
                                     @Override
                                     public void onSuccess() {
@@ -243,18 +272,27 @@
                                                 new ClearCredentialStateException(errorType,
                                                         errorMsg));
                                     }
+
+                                    @Override
+                                    public void onCancellable(ICancellationSignal cancellation) {
+                                        CompletableFuture<Void> future = futureRef.get();
+                                        if (future != null && future.isCancelled()) {
+                                            dispatchCancellationSignal(cancellation);
+                                        } else {
+                                            cancellationSink.set(cancellation);
+                                            callback.onProviderCancellable(cancellation);
+                                        }
+                                    }
                                 });
-                        cancellationSink.set(cancellationSignal);
                         return clearCredentialFuture;
                     } finally {
                         Binder.restoreCallingIdentity(originalCallingUidToken);
                     }
                 }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+        futureRef.set(connectThenExecute);
 
         connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
                 handleExecutionResponse(result, error, cancellationSink, callback)));
-
-        return cancellationSink.get();
     }
 
     private <T> void handleExecutionResponse(T result,
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index ed175ed..8fd0269 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -32,7 +32,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.credentials.CallingAppInfo;
-import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -43,6 +42,7 @@
 
 import java.util.ArrayList;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
@@ -88,6 +88,8 @@
 
     protected final SessionLifetime mSessionCallback;
 
+    private final Set<ComponentName> mEnabledProviders;
+
     @NonNull
     protected RequestSessionStatus mRequestSessionStatus =
             RequestSessionStatus.IN_PROGRESS;
@@ -108,6 +110,7 @@
             @NonNull T clientRequest, U clientCallback,
             @NonNull String requestType,
             CallingAppInfo callingAppInfo,
+            Set<ComponentName> enabledProviders,
             CancellationSignal cancellationSignal, long timestampStarted) {
         mContext = context;
         mLock = lock;
@@ -118,11 +121,12 @@
         mClientCallback = clientCallback;
         mRequestType = requestType;
         mClientAppInfo = callingAppInfo;
+        mEnabledProviders = enabledProviders;
         mCancellationSignal = cancellationSignal;
         mHandler = new Handler(Looper.getMainLooper(), null, true);
         mRequestId = new Binder();
         mCredentialManagerUi = new CredentialManagerUi(mContext,
-                mUserId, this);
+                mUserId, this, mEnabledProviders);
         mHybridService = context.getResources().getString(
                 R.string.config_defaultCredentialManagerHybridService);
         mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted, mRequestId,
@@ -174,7 +178,7 @@
     @Override // from CredentialManagerUiCallbacks
     public void onUiSelection(UserSelectionDialogResult selection) {
         if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
-            Log.i(TAG, "Request has already been completed. This is strange.");
+            Slog.w(TAG, "Request has already been completed. This is strange.");
             return;
         }
         if (isSessionCancelled()) {
@@ -182,13 +186,11 @@
             return;
         }
         String providerId = selection.getProviderId();
-        Log.i(TAG, "onUiSelection, providerId: " + providerId);
         ProviderSession providerSession = mProviders.get(providerId);
         if (providerSession == null) {
-            Log.i(TAG, "providerSession not found in onUiSelection");
+            Slog.w(TAG, "providerSession not found in onUiSelection. This is strange.");
             return;
         }
-        Log.i(TAG, "Provider session found");
         mRequestSessionMetric.collectMetricPerBrowsingSelect(selection,
                 providerSession.mProviderSessionMetric.getCandidatePhasePerProviderMetric());
         providerSession.onUiEntrySelected(selection.getEntryKey(),
@@ -242,15 +244,13 @@
     void getProviderDataAndInitiateUi() {
         ArrayList<ProviderData> providerDataList = getProviderDataForUi();
         if (!providerDataList.isEmpty()) {
-            Log.i(TAG, "provider list not empty about to initiate ui");
             launchUiWithProviderData(providerDataList);
         }
     }
 
     @NonNull
     protected ArrayList<ProviderData> getProviderDataForUi() {
-        Log.i(TAG, "In getProviderDataAndInitiateUi");
-        Log.i(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size());
+        Slog.d(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size());
         ArrayList<ProviderData> providerDataList = new ArrayList<>();
         mRequestSessionMetric.logCandidatePhaseMetrics(mProviders);
 
@@ -260,10 +260,8 @@
         }
 
         for (ProviderSession session : mProviders.values()) {
-            Log.i(TAG, "preparing data for : " + session.getComponentName());
             ProviderData providerData = session.prepareUiData();
             if (providerData != null) {
-                Log.i(TAG, "Provider data is not null");
                 providerDataList.add(providerData);
             }
         }
@@ -279,7 +277,7 @@
         mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(/*has_exception=*/ false,
                 ProviderStatusForMetrics.FINAL_SUCCESS);
         if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
-            Log.i(TAG, "Request has already been completed. This is strange.");
+            Slog.w(TAG, "Request has already been completed. This is strange.");
             return;
         }
         if (isSessionCancelled()) {
@@ -295,7 +293,7 @@
         } catch (RemoteException e) {
             mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(
                     /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE);
-            Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage());
+            Slog.e(TAG, "Issue while responding to client with a response : " + e);
             mRequestSessionMetric.logApiCalledAtFinish(
                     /*apiStatus=*/ ApiStatus.FAILURE.getMetricCode());
         }
@@ -306,13 +304,13 @@
      * Allows subclasses to directly finalize the call and set closing metrics on error completion.
      *
      * @param errorType the type of error given back in the flow
-     * @param errorMsg the error message given back in the flow
+     * @param errorMsg  the error message given back in the flow
      */
     protected void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
         mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(
                 /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE);
         if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
-            Log.i(TAG, "Request has already been completed. This is strange.");
+            Slog.w(TAG, "Request has already been completed. This is strange.");
             return;
         }
         if (isSessionCancelled()) {
@@ -325,7 +323,7 @@
         try {
             invokeClientCallbackError(errorType, errorMsg);
         } catch (RemoteException e) {
-            Log.i(TAG, "Issue while responding to client with error : " + e.getMessage());
+            Slog.e(TAG, "Issue while responding to client with error : " + e);
         }
         boolean isUserCanceled = errorType.contains(MetricUtilities.USER_CANCELED_SUBSTRING);
         mRequestSessionMetric.logFailureOrUserCancel(isUserCanceled);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
index 474df98..950ec77 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
@@ -32,22 +32,25 @@
 
 final class BooleanPolicySerializer extends PolicySerializer<Boolean> {
 
+    private static final String ATTR_VALUE = "value";
+
+    private static final String TAG = "BooleanPolicySerializer";
+
     @Override
-    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeName,
-            @NonNull Boolean value)
+    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull Boolean value)
             throws IOException {
         Objects.requireNonNull(value);
-        serializer.attributeBoolean(/* namespace= */ null, attributeName, value);
+        serializer.attributeBoolean(/* namespace= */ null, ATTR_VALUE, value);
     }
 
     @Nullable
     @Override
-    BooleanPolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) {
+    BooleanPolicyValue readFromXml(TypedXmlPullParser parser) {
         try {
             return new BooleanPolicyValue(
-                    parser.getAttributeBoolean(/* namespace= */ null, attributeName));
+                    parser.getAttributeBoolean(/* namespace= */ null, ATTR_VALUE));
         } catch (XmlPullParserException e) {
-            Log.e(DevicePolicyEngine.TAG, "Error parsing Boolean policy value", e);
+            Log.e(TAG, "Error parsing Boolean policy value", e);
             return null;
         }
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
index c79aac7..ee73f8a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
@@ -53,6 +53,10 @@
 //  rather than in its own files.
 final class BundlePolicySerializer extends PolicySerializer<Bundle> {
 
+    private static final String TAG = "BundlePolicySerializer";
+
+    private static final String ATTR_FILE_NAME = "file-name";
+
     private static final String RESTRICTIONS_FILE_PREFIX = "AppRestrictions_";
     private static final String XML_SUFFIX = ".xml";
 
@@ -72,7 +76,7 @@
 
     @Override
     void saveToXml(@NonNull PolicyKey policyKey, TypedXmlSerializer serializer,
-            String attributeName, @NonNull Bundle value) throws IOException {
+            @NonNull Bundle value) throws IOException {
         Objects.requireNonNull(value);
         Objects.requireNonNull(policyKey);
         if (!(policyKey instanceof PackagePolicyKey)) {
@@ -82,13 +86,13 @@
         String packageName = ((PackagePolicyKey) policyKey).getPackageName();
         String fileName = packageToRestrictionsFileName(packageName, value);
         writeApplicationRestrictionsLAr(fileName, value);
-        serializer.attribute(/* namespace= */ null, attributeName, fileName);
+        serializer.attribute(/* namespace= */ null, ATTR_FILE_NAME, fileName);
     }
 
     @Nullable
     @Override
-    BundlePolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) {
-        String fileName = parser.getAttributeValue(/* namespace= */ null, attributeName);
+    BundlePolicyValue readFromXml(TypedXmlPullParser parser) {
+        String fileName = parser.getAttributeValue(/* namespace= */ null, ATTR_FILE_NAME);
 
         return new BundlePolicyValue(readApplicationRestrictions(fileName));
     }
@@ -119,7 +123,7 @@
             final TypedXmlPullParser parser = Xml.resolvePullParser(fis);
             XmlUtils.nextElement(parser);
             if (parser.getEventType() != XmlPullParser.START_TAG) {
-                Slog.e(DevicePolicyEngine.TAG, "Unable to read restrictions file "
+                Slog.e(TAG, "Unable to read restrictions file "
                         + restrictionsFile.getBaseFile());
                 return restrictions;
             }
@@ -127,7 +131,7 @@
                 readEntry(restrictions, values, parser);
             }
         } catch (IOException | XmlPullParserException e) {
-            Slog.w(DevicePolicyEngine.TAG, "Error parsing " + restrictionsFile.getBaseFile(), e);
+            Slog.w(TAG, "Error parsing " + restrictionsFile.getBaseFile(), e);
         } finally {
             IoUtils.closeQuietly(fis);
         }
@@ -209,7 +213,7 @@
             restrictionsFile.finishWrite(fos);
         } catch (Exception e) {
             restrictionsFile.failWrite(fos);
-            Slog.e(DevicePolicyEngine.TAG, "Error writing application restrictions list", e);
+            Slog.e(TAG, "Error writing application restrictions list", e);
         }
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java
index d1c6bcb..6303a1a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java
@@ -30,30 +30,31 @@
 import java.util.Objects;
 
 final class ComponentNamePolicySerializer extends PolicySerializer<ComponentName> {
-    private static final String ATTR_PACKAGE_NAME = ":package-name";
-    private static final String ATTR_CLASS_NAME = ":class-name";
+
+    private static final String TAG = "ComponentNamePolicySerializer";
+
+    private static final String ATTR_PACKAGE_NAME = "package-name";
+    private static final String ATTR_CLASS_NAME = "class-name";
 
     @Override
-    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeNamePrefix,
+    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
             @NonNull ComponentName value) throws IOException {
         Objects.requireNonNull(value);
         serializer.attribute(
-                /* namespace= */ null,
-                attributeNamePrefix + ATTR_PACKAGE_NAME, value.getPackageName());
+                /* namespace= */ null, ATTR_PACKAGE_NAME, value.getPackageName());
         serializer.attribute(
-                /* namespace= */ null,
-                attributeNamePrefix + ATTR_CLASS_NAME, value.getClassName());
+                /* namespace= */ null, ATTR_CLASS_NAME, value.getClassName());
     }
 
     @Nullable
     @Override
-    ComponentNamePolicyValue readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
+    ComponentNamePolicyValue readFromXml(TypedXmlPullParser parser) {
         String packageName = parser.getAttributeValue(
-                /* namespace= */ null, attributeNamePrefix + ATTR_PACKAGE_NAME);
+                /* namespace= */ null, ATTR_PACKAGE_NAME);
         String className = parser.getAttributeValue(
-                /* namespace= */ null, attributeNamePrefix + ATTR_CLASS_NAME);
+                /* namespace= */ null, ATTR_CLASS_NAME);
         if (packageName == null || className == null) {
-            Log.e(DevicePolicyEngine.TAG, "Error parsing ComponentName policy.");
+            Log.e(TAG, "Error parsing ComponentName policy.");
             return null;
         }
         return new ComponentNamePolicyValue(new ComponentName(packageName, className));
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index d4f4b72..f111a95 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -1181,7 +1181,8 @@
         private static final String DEVICE_POLICIES_XML = "device_policy_state.xml";
         private static final String TAG_LOCAL_POLICY_ENTRY = "local-policy-entry";
         private static final String TAG_GLOBAL_POLICY_ENTRY = "global-policy-entry";
-        private static final String TAG_ADMINS_POLICY_ENTRY = "admins-policy-entry";
+        private static final String TAG_POLICY_STATE_ENTRY = "policy-state-entry";
+        private static final String TAG_POLICY_KEY_ENTRY = "policy-key-entry";
         private static final String TAG_ENFORCING_ADMINS_ENTRY = "enforcing-admins-entry";
         private static final String ATTR_USER_ID = "user-id";
 
@@ -1236,11 +1237,14 @@
                         serializer.startTag(/* namespace= */ null, TAG_LOCAL_POLICY_ENTRY);
 
                         serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, userId);
-                        policy.getKey().saveToXml(serializer);
 
-                        serializer.startTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+                        serializer.startTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY);
+                        policy.getKey().saveToXml(serializer);
+                        serializer.endTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY);
+
+                        serializer.startTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY);
                         policy.getValue().saveToXml(serializer);
-                        serializer.endTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+                        serializer.endTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY);
 
                         serializer.endTag(/* namespace= */ null, TAG_LOCAL_POLICY_ENTRY);
                     }
@@ -1253,11 +1257,13 @@
                 for (Map.Entry<PolicyKey, PolicyState<?>> policy : mGlobalPolicies.entrySet()) {
                     serializer.startTag(/* namespace= */ null, TAG_GLOBAL_POLICY_ENTRY);
 
+                    serializer.startTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY);
                     policy.getKey().saveToXml(serializer);
+                    serializer.endTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY);
 
-                    serializer.startTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+                    serializer.startTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY);
                     policy.getValue().saveToXml(serializer);
-                    serializer.endTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+                    serializer.endTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY);
 
                     serializer.endTag(/* namespace= */ null, TAG_GLOBAL_POLICY_ENTRY);
                 }
@@ -1323,28 +1329,56 @@
         private void readLocalPoliciesInner(TypedXmlPullParser parser)
                 throws XmlPullParserException, IOException {
             int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
-            PolicyKey policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
-            if (!mLocalPolicies.contains(userId)) {
-                mLocalPolicies.put(userId, new HashMap<>());
+            PolicyKey policyKey = null;
+            PolicyState<?> policyState = null;
+            int outerDepth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                String tag = parser.getName();
+                switch (tag) {
+                    case TAG_POLICY_KEY_ENTRY:
+                        policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
+                        break;
+                    case TAG_POLICY_STATE_ENTRY:
+                        policyState = PolicyState.readFromXml(parser);
+                        break;
+                    default:
+                        Log.e(TAG, "Unknown tag for local policy entry" + tag);
+                }
             }
-            PolicyState<?> adminsPolicy = parseAdminsPolicy(parser);
-            if (adminsPolicy != null) {
-                mLocalPolicies.get(userId).put(policyKey, adminsPolicy);
+
+            if (policyKey != null && policyState != null) {
+                if (!mLocalPolicies.contains(userId)) {
+                    mLocalPolicies.put(userId, new HashMap<>());
+                }
+                mLocalPolicies.get(userId).put(policyKey, policyState);
             } else {
-                Log.e(TAG,
-                        "Error parsing file, " + policyKey + "doesn't have an " + "AdminsPolicy.");
+                Log.e(TAG, "Error parsing local policy");
             }
         }
 
         private void readGlobalPoliciesInner(TypedXmlPullParser parser)
                 throws IOException, XmlPullParserException {
-            PolicyKey policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
-            PolicyState<?> adminsPolicy = parseAdminsPolicy(parser);
-            if (adminsPolicy != null) {
-                mGlobalPolicies.put(policyKey, adminsPolicy);
+            PolicyKey policyKey = null;
+            PolicyState<?> policyState = null;
+            int outerDepth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                String tag = parser.getName();
+                switch (tag) {
+                    case TAG_POLICY_KEY_ENTRY:
+                        policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
+                        break;
+                    case TAG_POLICY_STATE_ENTRY:
+                        policyState = PolicyState.readFromXml(parser);
+                        break;
+                    default:
+                        Log.e(TAG, "Unknown tag for local policy entry" + tag);
+                }
+            }
+
+            if (policyKey != null && policyState != null) {
+                mGlobalPolicies.put(policyKey, policyState);
             } else {
-                Log.e(TAG,
-                        "Error parsing file, " + policyKey + "doesn't have an " + "AdminsPolicy.");
+                Log.e(TAG, "Error parsing global policy");
             }
         }
 
@@ -1356,20 +1390,5 @@
             }
             mEnforcingAdmins.get(admin.getUserId()).add(admin);
         }
-
-        @Nullable
-        private PolicyState<?> parseAdminsPolicy(TypedXmlPullParser parser)
-                throws XmlPullParserException, IOException {
-            int outerDepth = parser.getDepth();
-            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
-                String tag = parser.getName();
-                if (tag.equals(TAG_ADMINS_POLICY_ENTRY)) {
-                    return PolicyState.readFromXml(parser);
-                }
-                Log.e(TAG, "Unknown tag " + tag);
-            }
-            Log.e(TAG, "Error parsing file, AdminsPolicy not found");
-            return null;
-        }
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ecd1d3f..b09bf6ad 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -563,6 +563,8 @@
 
     private static final int REQUEST_PROFILE_OFF_DEADLINE = 5572;
 
+    private static final int MAX_PROFILE_NAME_LENGTH = 200;
+
     private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1);
 
     private static final long EXPIRATION_GRACE_PERIOD_MS = 5 * MS_PER_DAY; // 5 days, in ms
@@ -10380,8 +10382,10 @@
         Preconditions.checkCallAuthorization(
                 isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
+        final String truncatedProfileName =
+                profileName.substring(0, Math.min(profileName.length(), MAX_PROFILE_NAME_LENGTH));
         mInjector.binderWithCleanCallingIdentity(() -> {
-            mUserManager.setUserName(caller.getUserId(), profileName);
+            mUserManager.setUserName(caller.getUserId(), truncatedProfileName);
             DevicePolicyEventLogger
                     .createEvent(DevicePolicyEnums.SET_PROFILE_NAME)
                     .setAdmin(caller.getComponentName())
@@ -11387,7 +11391,7 @@
         for (PolicyKey key : keys) {
             if (!(key instanceof IntentFilterPolicyKey)) {
                 throw new IllegalStateException("PolicyKey for PERSISTENT_PREFERRED_ACTIVITY is not"
-                        + "of type PersistentPreferredActivityPolicyKey");
+                        + "of type IntentFilterPolicyKey");
             }
             IntentFilterPolicyKey parsedKey =
                     (IntentFilterPolicyKey) key;
@@ -16622,10 +16626,6 @@
         SENSOR_PERMISSIONS.add(Manifest.permission.BACKGROUND_CAMERA);
         SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_BACKGROUND_AUDIO);
         SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
-        SENSOR_PERMISSIONS.add(
-                Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE);
-        SENSOR_PERMISSIONS.add(
-                    Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND);
     }
 
     private boolean canGrantPermission(CallerIdentity caller, String permission,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
index bff6d32..45a2d2a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
@@ -32,21 +32,25 @@
 
 final class IntegerPolicySerializer extends PolicySerializer<Integer> {
 
+    private static final String TAG = "IntegerPolicySerializer";
+
+    private static final String ATTR_VALUE = "value";
+
     @Override
-    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeName,
+    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
             @NonNull Integer value) throws IOException {
         Objects.requireNonNull(value);
-        serializer.attributeInt(/* namespace= */ null, attributeName, value);
+        serializer.attributeInt(/* namespace= */ null, ATTR_VALUE, value);
     }
 
     @Nullable
     @Override
-    IntegerPolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) {
+    IntegerPolicyValue readFromXml(TypedXmlPullParser parser) {
         try {
             return new IntegerPolicyValue(
-                    parser.getAttributeInt(/* namespace= */ null, attributeName));
+                    parser.getAttributeInt(/* namespace= */ null, ATTR_VALUE));
         } catch (XmlPullParserException e) {
-            Log.e(DevicePolicyEngine.TAG, "Error parsing Integer policy value", e);
+            Log.e(TAG, "Error parsing Integer policy value", e);
             return null;
         }
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
index 3265b61..0f6f3c5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
@@ -32,12 +32,14 @@
 
 final class LockTaskPolicySerializer extends PolicySerializer<LockTaskPolicy> {
 
-    private static final String ATTR_PACKAGES = ":packages";
+    private static final String TAG = "LockTaskPolicySerializer";
+
+    private static final String ATTR_PACKAGES = "packages";
     private static final String ATTR_PACKAGES_SEPARATOR = ";";
-    private static final String ATTR_FLAGS = ":flags";
+    private static final String ATTR_FLAGS = "flags";
 
     @Override
-    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeNamePrefix,
+    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
             @NonNull LockTaskPolicy value) throws IOException {
         Objects.requireNonNull(value);
         if (value.getPackages() == null || value.getPackages().isEmpty()) {
@@ -46,31 +48,31 @@
         }
         serializer.attribute(
                 /* namespace= */ null,
-                attributeNamePrefix + ATTR_PACKAGES,
+                ATTR_PACKAGES,
                 String.join(ATTR_PACKAGES_SEPARATOR, value.getPackages()));
         serializer.attributeInt(
                 /* namespace= */ null,
-                attributeNamePrefix + ATTR_FLAGS,
+                ATTR_FLAGS,
                 value.getFlags());
     }
 
     @Override
-    LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
+    LockTaskPolicy readFromXml(TypedXmlPullParser parser) {
         String packagesStr = parser.getAttributeValue(
                 /* namespace= */ null,
-                attributeNamePrefix + ATTR_PACKAGES);
+                ATTR_PACKAGES);
         if (packagesStr == null) {
-            Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value.");
+            Log.e(TAG, "Error parsing LockTask policy value.");
             return null;
         }
         Set<String> packages = Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR));
         try {
             int flags = parser.getAttributeInt(
                     /* namespace= */ null,
-                    attributeNamePrefix + ATTR_FLAGS);
+                    ATTR_FLAGS);
             return new LockTaskPolicy(packages, flags);
         } catch (XmlPullParserException e) {
-            Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value", e);
+            Log.e(TAG, "Error parsing LockTask policy value", e);
             return null;
         }
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java
index f77d051..522c4b5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java
@@ -32,21 +32,25 @@
 
 final class LongPolicySerializer extends PolicySerializer<Long> {
 
+    private static final String TAG = "LongPolicySerializer";
+
+    private static final String ATTR_VALUE = "value";
+
     @Override
-    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeName,
+    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
             @NonNull Long value) throws IOException {
         Objects.requireNonNull(value);
-        serializer.attributeLong(/* namespace= */ null, attributeName, value);
+        serializer.attributeLong(/* namespace= */ null, ATTR_VALUE, value);
     }
 
     @Nullable
     @Override
-    LongPolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) {
+    LongPolicyValue readFromXml(TypedXmlPullParser parser) {
         try {
             return new LongPolicyValue(
-                    parser.getAttributeLong(/* namespace= */ null, attributeName));
+                    parser.getAttributeLong(/* namespace= */ null, ATTR_VALUE));
         } catch (XmlPullParserException e) {
-            Log.e(DevicePolicyEngine.TAG, "Error parsing Long policy value", e);
+            Log.e(TAG, "Error parsing Long policy value", e);
             return null;
         }
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index a15aa53..509a66b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -531,7 +531,6 @@
     }
 
     void saveToXml(TypedXmlSerializer serializer) throws IOException {
-        // TODO: here and elsewhere, add tags to ensure attributes aren't overridden by duplication.
         mPolicyKey.saveToXml(serializer);
     }
 
@@ -554,14 +553,14 @@
         return genericPolicyDefinition.mPolicyKey.readFromXml(parser);
     }
 
-    void savePolicyValueToXml(TypedXmlSerializer serializer, String attributeName, V value)
+    void savePolicyValueToXml(TypedXmlSerializer serializer, V value)
             throws IOException {
-        mPolicySerializer.saveToXml(mPolicyKey, serializer, attributeName, value);
+        mPolicySerializer.saveToXml(mPolicyKey, serializer, value);
     }
 
     @Nullable
-    PolicyValue<V> readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName) {
-        return mPolicySerializer.readFromXml(parser, attributeName);
+    PolicyValue<V> readPolicyValueFromXml(TypedXmlPullParser parser) {
+        return mPolicySerializer.readFromXml(parser);
     }
 
     @Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
index 0ef431f..5af2fa2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
@@ -26,8 +26,7 @@
 import java.io.IOException;
 
 abstract class PolicySerializer<V> {
-    abstract void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
-            String attributeName, @NonNull V value)
+    abstract void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull V value)
             throws IOException;
-    abstract PolicyValue<V> readFromXml(TypedXmlPullParser parser, String attributeName);
+    abstract PolicyValue<V> readFromXml(TypedXmlPullParser parser);
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index 3a792d8..741f209 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -35,11 +35,14 @@
  * Class containing all values set for a certain policy by different admins.
  */
 final class PolicyState<V> {
-    private static final String TAG_ADMIN_POLICY_ENTRY = "admin-policy-entry";
-    private static final String TAG_ENFORCING_ADMIN_ENTRY = "enforcing-admin-entry";
-    private static final String ATTR_POLICY_VALUE = "policy-value";
-    private static final String ATTR_RESOLVED_POLICY = "resolved-policy";
 
+    private static final String TAG = "PolicyState";
+    private static final String TAG_ADMIN_POLICY_ENTRY = "admin-policy-entry";
+
+    private static final String TAG_POLICY_DEFINITION_ENTRY = "policy-definition-entry";
+    private static final String TAG_RESOLVED_VALUE_ENTRY = "resolved-value-entry";
+    private static final String TAG_ENFORCING_ADMIN_ENTRY = "enforcing-admin-entry";
+    private static final String TAG_POLICY_VALUE_ENTRY = "policy-value-entry";
     private final PolicyDefinition<V> mPolicyDefinition;
     private final LinkedHashMap<EnforcingAdmin, PolicyValue<V>> mPoliciesSetByAdmins =
             new LinkedHashMap<>();
@@ -193,18 +196,24 @@
     }
 
     void saveToXml(TypedXmlSerializer serializer) throws IOException {
+        serializer.startTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
         mPolicyDefinition.saveToXml(serializer);
+        serializer.endTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
 
         if (mCurrentResolvedPolicy != null) {
+            serializer.startTag(/* namespace= */ null, TAG_RESOLVED_VALUE_ENTRY);
             mPolicyDefinition.savePolicyValueToXml(
-                    serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy.getValue());
+                    serializer, mCurrentResolvedPolicy.getValue());
+            serializer.endTag(/* namespace= */ null, TAG_RESOLVED_VALUE_ENTRY);
         }
 
         for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) {
             serializer.startTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY);
 
+            serializer.startTag(/* namespace= */ null, TAG_POLICY_VALUE_ENTRY);
             mPolicyDefinition.savePolicyValueToXml(
-                    serializer, ATTR_POLICY_VALUE, mPoliciesSetByAdmins.get(admin).getValue());
+                    serializer, mPoliciesSetByAdmins.get(admin).getValue());
+            serializer.endTag(/* namespace= */ null, TAG_POLICY_VALUE_ENTRY);
 
             serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY);
             admin.saveToXml(serializer);
@@ -217,32 +226,57 @@
     static <V> PolicyState<V> readFromXml(TypedXmlPullParser parser)
             throws IOException, XmlPullParserException {
 
-        PolicyDefinition<V> policyDefinition = PolicyDefinition.readFromXml(parser);
+        PolicyDefinition<V> policyDefinition = null;
 
-        PolicyValue<V> currentResolvedPolicy = policyDefinition.readPolicyValueFromXml(
-                parser, ATTR_RESOLVED_POLICY);
+        PolicyValue<V> currentResolvedPolicy = null;
 
         LinkedHashMap<EnforcingAdmin, PolicyValue<V>> policiesSetByAdmins = new LinkedHashMap<>();
         int outerDepth = parser.getDepth();
         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
             String tag = parser.getName();
-            if (TAG_ADMIN_POLICY_ENTRY.equals(tag)) {
-                PolicyValue<V> value = policyDefinition.readPolicyValueFromXml(
-                        parser, ATTR_POLICY_VALUE);
-                EnforcingAdmin admin;
-                int adminPolicyDepth = parser.getDepth();
-                if (XmlUtils.nextElementWithin(parser, adminPolicyDepth)
-                        && parser.getName().equals(TAG_ENFORCING_ADMIN_ENTRY)) {
-                    admin = EnforcingAdmin.readFromXml(parser);
-                    policiesSetByAdmins.put(admin, value);
-                }
-            } else {
-                Log.e(DevicePolicyEngine.TAG, "Unknown tag: " + tag);
+            switch (tag) {
+                case TAG_ADMIN_POLICY_ENTRY:
+                    PolicyValue<V> value = null;
+                    EnforcingAdmin admin = null;
+                    int adminPolicyDepth = parser.getDepth();
+                    while (XmlUtils.nextElementWithin(parser, adminPolicyDepth)) {
+                        String adminPolicyTag = parser.getName();
+                        switch (adminPolicyTag) {
+                            case TAG_ENFORCING_ADMIN_ENTRY:
+                                admin = EnforcingAdmin.readFromXml(parser);
+                                break;
+                            case TAG_POLICY_VALUE_ENTRY:
+                                value = policyDefinition.readPolicyValueFromXml(parser);
+                                break;
+                        }
+                    }
+                    if (admin != null && value != null) {
+                        policiesSetByAdmins.put(admin, value);
+                    } else {
+                        Log.e(TAG, "Error Parsing TAG_ADMIN_POLICY_ENTRY");
+                    }
+                    break;
+                case TAG_POLICY_DEFINITION_ENTRY:
+                    policyDefinition = PolicyDefinition.readFromXml(parser);
+                    break;
+
+                case TAG_RESOLVED_VALUE_ENTRY:
+                    currentResolvedPolicy = policyDefinition.readPolicyValueFromXml(parser);
+                    break;
+                default:
+                    Log.e(TAG, "Unknown tag: " + tag);
             }
         }
-        return new PolicyState<V>(policyDefinition, policiesSetByAdmins, currentResolvedPolicy);
+        if (policyDefinition != null) {
+            return new PolicyState<V>(policyDefinition, policiesSetByAdmins, currentResolvedPolicy);
+        } else {
+            Log.e("PolicyState", "Error parsing policyState");
+            return null;
+        }
     }
 
+
+
     PolicyDefinition<V> getPolicyDefinition() {
         return mPolicyDefinition;
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
index dc6592d..24d0521 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
@@ -36,21 +36,17 @@
     private static final String ATTR_VALUES_SEPARATOR = ";";
 
     @Override
-    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeNamePrefix,
+    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
             @NonNull Set<String> value) throws IOException {
         Objects.requireNonNull(value);
         serializer.attribute(
-                /* namespace= */ null,
-                attributeNamePrefix + ATTR_VALUES,
-                String.join(ATTR_VALUES_SEPARATOR, value));
+                /* namespace= */ null, ATTR_VALUES, String.join(ATTR_VALUES_SEPARATOR, value));
     }
 
     @Nullable
     @Override
-    PolicyValue<Set<String>> readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
-        String valuesStr = parser.getAttributeValue(
-                /* namespace= */ null,
-                attributeNamePrefix + ATTR_VALUES);
+    PolicyValue<Set<String>> readFromXml(TypedXmlPullParser parser) {
+        String valuesStr = parser.getAttributeValue(/* namespace= */ null, ATTR_VALUES);
         if (valuesStr == null) {
             Log.e(DevicePolicyEngine.TAG, "Error parsing StringSet policy value.");
             return null;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index dbff90a..7b502ce 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -127,6 +127,7 @@
 import com.android.server.connectivity.PacProxyService;
 import com.android.server.contentcapture.ContentCaptureManagerInternal;
 import com.android.server.coverage.CoverageService;
+import com.android.server.cpu.CpuMonitorService;
 import com.android.server.devicepolicy.DevicePolicyManagerService;
 import com.android.server.devicestate.DeviceStateManagerService;
 import com.android.server.display.DisplayManagerService;
@@ -1413,6 +1414,15 @@
         mSystemServiceManager.startService(RemoteProvisioningService.class);
         t.traceEnd();
 
+        // TODO(b/277600174): Start CpuMonitorService on all builds and not just on debuggable
+        // builds once the Android JobScheduler starts using this service.
+        if (Build.IS_DEBUGGABLE || Build.IS_ENG) {
+          // Service for CPU monitor.
+          t.traceBegin("CpuMonitorService");
+          mSystemServiceManager.startService(CpuMonitorService.class);
+          t.traceEnd();
+        }
+
         t.traceEnd(); // startCoreServices
     }
 
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index e416718..90cb352 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -29,6 +29,7 @@
 import com.android.server.appop.AppOpsCheckingServiceInterface
 import com.android.server.permission.access.appop.AppOpService
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.permission.PermissionService
 import com.android.server.pm.KnownPackages
 import com.android.server.pm.PackageManagerLocal
@@ -72,7 +73,7 @@
         userManagerService = UserManagerService.getInstance()
         systemConfig = SystemConfig.getInstance()
 
-        val userIds = IntSet(userManagerService.userIdsIncludingPreCreated)
+        val userIds = MutableIntSet(userManagerService.userIdsIncludingPreCreated)
         val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
         val knownPackages = packageManagerInternal.knownPackages
         val isLeanback = systemConfig.isLeanback
@@ -82,7 +83,7 @@
         val permissionAllowlist = systemConfig.permissionAllowlist
         val implicitToSourcePermissions = systemConfig.implicitToSourcePermissions
 
-        val state = AccessState()
+        val state = MutableAccessState()
         policy.initialize(
             state, userIds, packageStates, disabledSystemPackageStates, knownPackages, isLeanback,
             configPermissions, privilegedPermissionAllowlistPackages, permissionAllowlist,
@@ -104,7 +105,7 @@
         get() = PackageManager.FEATURE_LEANBACK in availableFeatures
 
     private val SystemConfig.privilegedPermissionAllowlistPackages: IndexedListSet<String>
-        get() = IndexedListSet<String>().apply {
+        get() = MutableIndexedListSet<String>().apply {
             this += "android"
             if (PackageManager.FEATURE_AUTOMOTIVE in availableFeatures) {
                 // Note that SystemProperties.get(String, String) forces returning an empty string
@@ -117,14 +118,16 @@
         }
 
     private val SystemConfig.implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>
-        get() = IndexedMap<String, IndexedListSet<String>>().apply {
+        @Suppress("UNCHECKED_CAST")
+        get() = MutableIndexedMap<String, MutableIndexedListSet<String>>().apply {
             splitPermissions.forEach { splitPermissionInfo ->
                 val sourcePermissionName = splitPermissionInfo.splitPermission
                 splitPermissionInfo.newPermissions.forEach { implicitPermissionName ->
-                    getOrPut(implicitPermissionName) { IndexedListSet() } += sourcePermissionName
+                    getOrPut(implicitPermissionName) { MutableIndexedListSet() } +=
+                        sourcePermissionName
                 }
             }
-        }
+        } as IndexedMap<String, IndexedListSet<String>>
 
     fun getDecision(subject: AccessUri, `object`: AccessUri): Int =
         getState {
@@ -222,7 +225,7 @@
         get() = withUnfilteredSnapshot().use { it.packageStates to it.disabledSystemPackageStates }
 
     private val PackageManagerInternal.knownPackages: IntMap<Array<String>>
-        get() = IntMap<Array<String>>().apply {
+        get() = MutableIntMap<Array<String>>().apply {
             this[KnownPackages.PACKAGE_INSTALLER] = getKnownPackageNames(
                 KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM
             )
@@ -269,7 +272,7 @@
         contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
         synchronized(stateLock) {
             val oldState = state
-            val newState = oldState.copy()
+            val newState = oldState.toMutable()
             MutateStateScope(oldState, newState).action()
             persistence.write(newState)
             state = newState
diff --git a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
index 4182ecf..5108a04 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
@@ -23,11 +23,13 @@
 import android.os.UserHandle
 import android.util.AtomicFile
 import android.util.Log
+import android.util.SparseLongArray
 import com.android.internal.annotations.GuardedBy
 import com.android.internal.os.BackgroundThread
 import com.android.modules.utils.BinaryXmlPullParser
 import com.android.modules.utils.BinaryXmlSerializer
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.util.PermissionApex
 import com.android.server.permission.access.util.parseBinaryXml
 import com.android.server.permission.access.util.readWithReserveCopy
@@ -41,9 +43,9 @@
 ) {
     private val scheduleLock = Any()
     @GuardedBy("scheduleLock")
-    private val pendingMutationTimesMillis = IntLongMap()
+    private val pendingMutationTimesMillis = SparseLongArray()
     @GuardedBy("scheduleLock")
-    private val pendingStates = IntMap<AccessState>()
+    private val pendingStates = MutableIntMap<AccessState>()
     @GuardedBy("scheduleLock")
     private lateinit var writeHandler: WriteHandler
 
@@ -56,14 +58,14 @@
     /**
      * Reads the state either from the disk or migrate legacy data when the data files are missing.
      */
-    fun read(state: AccessState) {
+    fun read(state: MutableAccessState) {
         readSystemState(state)
         state.systemState.userIds.forEachIndexed { _, userId ->
             readUserState(state, userId)
         }
     }
 
-    private fun readSystemState(state: AccessState) {
+    private fun readSystemState(state: MutableAccessState) {
         val fileExists = systemFile.parse {
             // This is the canonical way to call an extension function in a different class.
             // TODO(b/259469752): Use context receiver for this when it becomes stable.
@@ -72,25 +74,18 @@
 
         if (!fileExists) {
             policy.migrateSystemState(state)
-            state.systemState.apply {
-                requestWrite()
-                write(state, UserHandle.USER_ALL)
-            }
+            state.systemState.write(state, UserHandle.USER_ALL)
         }
     }
 
-
-    private fun readUserState(state: AccessState, userId: Int) {
+    private fun readUserState(state: MutableAccessState, userId: Int) {
         val fileExists = getUserFile(userId).parse {
             with(policy) { parseUserState(state, userId) }
         }
 
         if (!fileExists) {
             policy.migrateUserState(state, userId)
-            state.userStates[userId].apply {
-                requestWrite()
-                write(state, userId)
-            }
+            state.userStates[userId]!!.write(state, userId)
         }
     }
 
@@ -119,11 +114,7 @@
     private fun WritableState.write(state: AccessState, userId: Int) {
         when (val writeMode = writeMode) {
             WriteMode.NONE -> {}
-            WriteMode.SYNC -> {
-                synchronized(scheduleLock) { pendingStates[userId] = state }
-                writePendingState(userId)
-            }
-            WriteMode.ASYNC -> {
+            WriteMode.ASYNCHRONOUS -> {
                 synchronized(scheduleLock) {
                     writeHandler.removeMessages(userId)
                     pendingStates[userId] = state
@@ -142,6 +133,10 @@
                     }
                 }
             }
+            WriteMode.SYNCHRONOUS -> {
+                synchronized(scheduleLock) { pendingStates[userId] = state }
+                writePendingState(userId)
+            }
             else -> error(writeMode)
         }
     }
@@ -151,7 +146,7 @@
             val state: AccessState?
             synchronized(scheduleLock) {
                 pendingMutationTimesMillis -= userId
-                state = pendingStates.removeReturnOld(userId)
+                state = pendingStates.remove(userId)
                 writeHandler.removeMessages(userId)
             }
             if (state == null) {
@@ -201,16 +196,6 @@
     }
 
     private inner class WriteHandler(looper: Looper) : Handler(looper) {
-        fun writeAtTime(userId: Int, timeMillis: Long) {
-            removeMessages(userId)
-            val message = obtainMessage(userId)
-            sendMessageDelayed(message, timeMillis)
-        }
-
-        fun cancelWrite(userId: Int) {
-            removeMessages(userId)
-        }
-
         override fun handleMessage(message: Message) {
             val userId = message.what
             writePendingState(userId)
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 7e106b0..9612f28 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -23,6 +23,8 @@
 import com.android.server.permission.access.appop.AppIdAppOpPolicy
 import com.android.server.permission.access.appop.PackageAppOpPolicy
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.IndexedMap
 import com.android.server.permission.access.permission.AppIdPermissionPolicy
 import com.android.server.permission.access.util.attributeInt
 import com.android.server.permission.access.util.attributeInterned
@@ -37,14 +39,16 @@
 class AccessPolicy private constructor(
     private val schemePolicies: IndexedMap<String, IndexedMap<String, SchemePolicy>>
 ) {
+    @Suppress("UNCHECKED_CAST")
     constructor() : this(
-        IndexedMap<String, IndexedMap<String, SchemePolicy>>().apply {
-            fun addPolicy(policy: SchemePolicy) =
-                getOrPut(policy.subjectScheme) { IndexedMap() }.put(policy.objectScheme, policy)
+        MutableIndexedMap<String, MutableIndexedMap<String, SchemePolicy>>().apply {
+            fun addPolicy(policy: SchemePolicy) {
+                getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] = policy
+            }
             addPolicy(AppIdPermissionPolicy())
             addPolicy(AppIdAppOpPolicy())
             addPolicy(PackageAppOpPolicy())
-        }
+        } as IndexedMap<String, IndexedMap<String, SchemePolicy>>
     )
 
     fun getSchemePolicy(subjectScheme: String, objectScheme: String): SchemePolicy =
@@ -60,7 +64,7 @@
     }
 
     fun initialize(
-        state: AccessState,
+        state: MutableAccessState,
         userIds: IntSet,
         packageStates: Map<String, PackageState>,
         disabledSystemPackageStates: Map<String, PackageState>,
@@ -71,25 +75,24 @@
         permissionAllowlist: PermissionAllowlist,
         implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>
     ) {
-        state.systemState.apply {
-            this.userIds += userIds
-            this.packageStates = packageStates
-            this.disabledSystemPackageStates = disabledSystemPackageStates
+        state.mutateSystemState(WriteMode.NONE).apply {
+            mutateUserIds() += userIds
+            setPackageStates(packageStates)
+            setDisabledSystemPackageStates(disabledSystemPackageStates)
             packageStates.forEach { (_, packageState) ->
-                appIds.getOrPut(packageState.appId) { IndexedListSet() }
+                mutateAppIdPackageNames()
+                    .mutateOrPut(packageState.appId) { MutableIndexedListSet() }
                     .add(packageState.packageName)
             }
-            this.knownPackages = knownPackages
-            this.isLeanback = isLeanback
-            this.configPermissions = configPermissions
-            this.privilegedPermissionAllowlistPackages = privilegedPermissionAllowlistPackages
-            this.permissionAllowlist = permissionAllowlist
-            this.implicitToSourcePermissions = implicitToSourcePermissions
+            setKnownPackages(knownPackages)
+            setLeanback(isLeanback)
+            setConfigPermissions(configPermissions)
+            setPrivilegedPermissionAllowlistPackages(privilegedPermissionAllowlistPackages)
+            setPermissionAllowlist(permissionAllowlist)
+            setImplicitToSourcePermissions(implicitToSourcePermissions)
         }
-        state.userStates.apply {
-            userIds.forEachIndexed { _, userId ->
-                this[userId] = UserState()
-            }
+        state.mutateUserStatesNoWrite().apply {
+            userIds.forEachIndexed { _, userId -> this[userId] = MutableUserState() }
         }
     }
 
@@ -106,8 +109,8 @@
     }
 
     fun MutateStateScope.onUserAdded(userId: Int) {
-        newState.systemState.userIds += userId
-        newState.userStates[userId] = UserState()
+        newState.mutateSystemState(WriteMode.NONE).mutateUserIds() += userId
+        newState.mutateUserStatesNoWrite()[userId] = MutableUserState()
         forEachSchemePolicy {
             with(it) { onUserAdded(userId) }
         }
@@ -117,8 +120,8 @@
     }
 
     fun MutateStateScope.onUserRemoved(userId: Int) {
-        newState.systemState.userIds -= userId
-        newState.userStates -= userId
+        newState.mutateSystemState(WriteMode.NONE).mutateUserIds() -= userId
+        newState.mutateUserStatesNoWrite() -= userId
         forEachSchemePolicy {
             with(it) { onUserRemoved(userId) }
         }
@@ -131,20 +134,20 @@
         volumeUuid: String?,
         isSystemUpdated: Boolean
     ) {
-        val addedAppIds = IntSet()
-        newState.systemState.apply {
-            this.packageStates = packageStates
-            this.disabledSystemPackageStates = disabledSystemPackageStates
+        val addedAppIds = MutableIntSet()
+        newState.mutateSystemState(WriteMode.NONE).apply {
+            setPackageStates(packageStates)
+            setDisabledSystemPackageStates(disabledSystemPackageStates)
             packageStates.forEach { (packageName, packageState) ->
                 if (packageState.volumeUuid == volumeUuid) {
                     val appId = packageState.appId
-                    appIds.getOrPut(appId) {
+                    mutateAppIdPackageNames().mutateOrPut(appId) {
                         addedAppIds += appId
-                        IndexedListSet()
+                        MutableIndexedListSet()
                     } += packageName
                 }
             }
-            this.knownPackages = knownPackages
+            setKnownPackages(knownPackages)
         }
         addedAppIds.forEachIndexed { _, appId ->
             forEachSchemePolicy {
@@ -176,14 +179,14 @@
         }
         val appId = packageState.appId
         var isAppIdAdded = false
-        newState.systemState.apply {
-            this.packageStates = packageStates
-            this.disabledSystemPackageStates = disabledSystemPackageStates
-            appIds.getOrPut(appId) {
+        newState.mutateSystemState(WriteMode.NONE).apply {
+            setPackageStates(packageStates)
+            setDisabledSystemPackageStates(disabledSystemPackageStates)
+            mutateAppIdPackageNames().mutateOrPut(appId) {
                 isAppIdAdded = true
-                IndexedListSet()
+                MutableIndexedListSet()
             } += packageName
-            this.knownPackages = knownPackages
+            setKnownPackages(knownPackages)
         }
         if (isAppIdAdded) {
             forEachSchemePolicy {
@@ -210,17 +213,17 @@
             "Removed package $packageName is still in packageStates in onPackageRemoved()"
         }
         var isAppIdRemoved = false
-        newState.systemState.apply {
-            this.packageStates = packageStates
-            this.disabledSystemPackageStates = disabledSystemPackageStates
-            appIds[appId]?.apply {
+        newState.mutateSystemState(WriteMode.NONE).apply {
+            setPackageStates(packageStates)
+            setDisabledSystemPackageStates(disabledSystemPackageStates)
+            mutateAppIdPackageNames().mutate(appId)?.apply {
                 this -= packageName
                 if (isEmpty()) {
-                    appIds -= appId
+                    mutateAppIdPackageNames() -= appId
                     isAppIdRemoved = true
                 }
             }
-            this.knownPackages = knownPackages
+            setKnownPackages(knownPackages)
         }
         forEachSchemePolicy {
             with(it) { onPackageRemoved(packageName, appId) }
@@ -230,9 +233,10 @@
                 with(it) { onAppIdRemoved(appId) }
             }
         }
-        newState.userStates.forEachIndexed { _, _, userState ->
-            userState.packageVersions -= packageName
-            userState.requestWrite()
+        newState.userStates.forEachIndexed { userStateIndex, _, userState ->
+            if (packageName in userState.packageVersions) {
+                newState.mutateUserStateAt(userStateIndex).mutatePackageVersions() -= packageName
+            }
         }
     }
 
@@ -243,10 +247,10 @@
         packageName: String,
         userId: Int
     ) {
-        newState.systemState.apply {
-            this.packageStates = packageStates
-            this.disabledSystemPackageStates = disabledSystemPackageStates
-            this.knownPackages = knownPackages
+        newState.mutateSystemState(WriteMode.NONE).apply {
+            setPackageStates(packageStates)
+            setDisabledSystemPackageStates(disabledSystemPackageStates)
+            setKnownPackages(knownPackages)
         }
         val packageState = packageStates[packageName]
         // TODO(zhanghai): STOPSHIP: Remove check before feature enable.
@@ -266,10 +270,10 @@
         appId: Int,
         userId: Int
     ) {
-        newState.systemState.apply {
-            this.packageStates = packageStates
-            this.disabledSystemPackageStates = disabledSystemPackageStates
-            this.knownPackages = knownPackages
+        newState.mutateSystemState(WriteMode.NONE).apply {
+            setPackageStates(packageStates)
+            setDisabledSystemPackageStates(disabledSystemPackageStates)
+            setKnownPackages(knownPackages)
         }
         forEachSchemePolicy {
             with(it) { onPackageUninstalled(packageName, appId, userId) }
@@ -277,19 +281,19 @@
     }
 
     fun MutateStateScope.onSystemReady() {
-        newState.systemState.isSystemReady = true
+        newState.mutateSystemState(WriteMode.NONE).setSystemReady(true)
         forEachSchemePolicy {
             with(it) { onSystemReady() }
         }
     }
 
-    fun migrateSystemState(state: AccessState) {
+    fun migrateSystemState(state: MutableAccessState) {
         forEachSchemePolicy {
             with(it) { migrateSystemState(state) }
         }
     }
 
-    fun migrateUserState(state: AccessState, userId: Int) {
+    fun migrateUserState(state: MutableAccessState, userId: Int) {
         forEachSchemePolicy {
             with(it) { migrateUserState(state, userId) }
         }
@@ -303,22 +307,17 @@
         val packageName = packageState.packageName
         // The version would be latest when the package is new to the system, e.g. newly
         // installed, first boot, or system apps added via OTA.
-        val version = newState.userStates[userId].packageVersions[packageName]
+        val version = newState.userStates[userId]!!.packageVersions[packageName]
         when {
-            version == null -> {
-                newState.userStates[userId].apply {
-                    packageVersions[packageName] = VERSION_LATEST
-                    requestWrite()
-                }
-            }
+            version == null ->
+                newState.mutateUserState(userId)!!.mutatePackageVersions()[packageName] =
+                    VERSION_LATEST
             version < VERSION_LATEST -> {
                 forEachSchemePolicy {
                     with(it) { upgradePackageState(packageState, userId, version) }
                 }
-                newState.userStates[userId].apply {
-                    packageVersions[packageName] = VERSION_LATEST
-                    requestWrite()
-                }
+                newState.mutateUserState(userId)!!.mutatePackageVersions()[packageName] =
+                    VERSION_LATEST
             }
             version == VERSION_LATEST -> {}
             else -> Log.w(
@@ -328,7 +327,7 @@
         }
     }
 
-    fun BinaryXmlPullParser.parseSystemState(state: AccessState) {
+    fun BinaryXmlPullParser.parseSystemState(state: MutableAccessState) {
         forEachTag {
             when (tagName) {
                 TAG_ACCESS -> {
@@ -351,7 +350,7 @@
         }
     }
 
-    fun BinaryXmlPullParser.parseUserState(state: AccessState, userId: Int) {
+    fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
         forEachTag {
             when (tagName) {
                 TAG_ACCESS -> {
@@ -376,33 +375,30 @@
         }
     }
 
-    private fun BinaryXmlPullParser.parsePackageVersions(state: AccessState, userId: Int) {
-        val userState = state.userStates[userId]
+    private fun BinaryXmlPullParser.parsePackageVersions(state: MutableAccessState, userId: Int) {
+        val userState = state.mutateUserState(userId, WriteMode.NONE)!!
+        val packageVersions = userState.mutatePackageVersions()
         forEachTag {
             when (tagName) {
-                TAG_PACKAGE -> parsePackageVersion(userState)
-                else -> Log.w(
-                    LOG_TAG,
-                    "Ignoring unknown tag $name when parsing package versions for user $userId"
-                )
+                TAG_PACKAGE -> parsePackageVersion(packageVersions)
+                else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing package versions")
             }
         }
-        userState.packageVersions.retainAllIndexed { _, packageName, _ ->
-            val hasPackage = packageName in state.systemState.packageStates
-            if (!hasPackage) {
-                Log.w(
-                    LOG_TAG,
-                    "Dropping unknown $packageName when parsing package versions for user $userId"
-                )
+        packageVersions.forEachReversedIndexed { packageVersionIndex, packageName, _ ->
+            if (packageName !in state.systemState.packageStates) {
+                Log.w(LOG_TAG, "Dropping unknown $packageName when parsing package versions")
+                packageVersions.removeAt(packageVersionIndex)
+                userState.requestWriteMode(WriteMode.ASYNCHRONOUS)
             }
-            hasPackage
         }
     }
 
-    private fun BinaryXmlPullParser.parsePackageVersion(userState: UserState) {
+    private fun BinaryXmlPullParser.parsePackageVersion(
+        packageVersions: MutableIndexedMap<String, Int>
+    ) {
         val packageName = getAttributeValueOrThrow(ATTR_NAME).intern()
         val version = getAttributeIntOrThrow(ATTR_VERSION)
-        userState.packageVersions[packageName] = version
+        packageVersions[packageName] = version
     }
 
     fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
@@ -410,14 +406,15 @@
             forEachSchemePolicy {
                 with(it) { serializeUserState(state, userId) }
             }
-
-            serializeVersions(state.userStates[userId])
+            serializePackageVersions(state.userStates[userId]!!.packageVersions)
         }
     }
 
-    private fun BinaryXmlSerializer.serializeVersions(userState: UserState) {
+    private fun BinaryXmlSerializer.serializePackageVersions(
+        packageVersions: IndexedMap<String, Int>
+    ) {
         tag(TAG_PACKAGE_VERSIONS) {
-            userState.packageVersions.forEachIndexed { _, packageName, version ->
+            packageVersions.forEachIndexed { _, packageName, version ->
                 tag(TAG_PACKAGE) {
                     attributeInterned(ATTR_NAME, packageName)
                     attributeInt(ATTR_VERSION, version)
@@ -430,8 +427,8 @@
         getSchemePolicy(subject.scheme, `object`.scheme)
 
     private inline fun forEachSchemePolicy(action: (SchemePolicy) -> Unit) {
-        schemePolicies.forEachValueIndexed { _, objectSchemePolicies ->
-            objectSchemePolicies.forEachValueIndexed { _, schemePolicy ->
+        schemePolicies.forEachIndexed { _, _, objectSchemePolicies ->
+            objectSchemePolicies.forEachIndexed { _, _, schemePolicy ->
                 action(schemePolicy)
             }
         }
@@ -491,9 +488,9 @@
 
     open fun MutateStateScope.onSystemReady() {}
 
-    open fun migrateSystemState(state: AccessState) {}
+    open fun migrateSystemState(state: MutableAccessState) {}
 
-    open fun migrateUserState(state: AccessState, userId: Int) {}
+    open fun migrateUserState(state: MutableAccessState, userId: Int) {}
 
     open fun MutateStateScope.upgradePackageState(
         packageState: PackageState,
@@ -501,11 +498,11 @@
         version: Int
     ) {}
 
-    open fun BinaryXmlPullParser.parseSystemState(state: AccessState) {}
+    open fun BinaryXmlPullParser.parseSystemState(state: MutableAccessState) {}
 
     open fun BinaryXmlSerializer.serializeSystemState(state: AccessState) {}
 
-    open fun BinaryXmlPullParser.parseUserState(state: AccessState, userId: Int) {}
+    open fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {}
 
     open fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {}
 }
diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt
index ea5411b..8240b97 100644
--- a/services/permission/java/com/android/server/permission/access/AccessState.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -18,128 +18,398 @@
 
 import android.content.pm.PermissionGroupInfo
 import com.android.server.SystemConfig
-import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.permission.Permission
 import com.android.server.pm.permission.PermissionAllowlist
 import com.android.server.pm.pkg.PackageState
 
-class AccessState private constructor(
-    val systemState: SystemState,
-    val userStates: IntMap<UserState>
+private typealias SystemStateReference = MutableReference<SystemState, MutableSystemState>
+
+typealias UserStates = IntReferenceMap<UserState, MutableUserState>
+typealias MutableUserStates = MutableIntReferenceMap<UserState, MutableUserState>
+private typealias UserStatesReference = MutableReference<UserStates, MutableUserStates>
+
+sealed class AccessState(
+    internal val systemStateReference: SystemStateReference,
+    internal val userStatesReference: UserStatesReference
+) : Immutable<MutableAccessState> {
+    val systemState: SystemState
+        get() = systemStateReference.get()
+
+    val userStates: UserStates
+        get() = userStatesReference.get()
+
+    override fun toMutable(): MutableAccessState = MutableAccessState(this)
+}
+
+class MutableAccessState private constructor(
+    systemStateReference: SystemStateReference,
+    userStatesReference: UserStatesReference
+) : AccessState(
+    systemStateReference,
+    userStatesReference
 ) {
     constructor() : this(
-        SystemState(),
-        IntMap()
+        SystemStateReference(MutableSystemState()),
+        UserStatesReference(MutableUserStates())
     )
 
-    fun copy(): AccessState = AccessState(
-        systemState.copy(),
-        userStates.copy { it.copy() }
+    internal constructor(accessState: AccessState) : this(
+        accessState.systemStateReference.toImmutable(),
+        accessState.userStatesReference.toImmutable()
     )
+
+    fun mutateSystemState(writeMode: Int = WriteMode.ASYNCHRONOUS): MutableSystemState =
+        systemStateReference.mutate().apply { requestWriteMode(writeMode) }
+
+    fun mutateUserStatesNoWrite(): MutableUserStates = userStatesReference.mutate()
+
+    fun mutateUserState(userId: Int, writeMode: Int = WriteMode.ASYNCHRONOUS): MutableUserState? =
+        mutateUserStatesNoWrite().mutate(userId)?.apply { requestWriteMode(writeMode) }
+
+    fun mutateUserStateAt(index: Int, writeMode: Int = WriteMode.ASYNCHRONOUS): MutableUserState =
+        mutateUserStatesNoWrite().mutateAt(index).apply { requestWriteMode(writeMode) }
 }
 
-class SystemState private constructor(
-    val userIds: IntSet,
-    var packageStates: Map<String, PackageState>,
-    var disabledSystemPackageStates: Map<String, PackageState>,
-    val appIds: IntMap<IndexedListSet<String>>,
-    // Mapping from KnownPackages keys to package names.
-    var knownPackages: IntMap<Array<String>>,
-    var isLeanback: Boolean,
-    var configPermissions: Map<String, SystemConfig.PermissionEntry>,
-    var privilegedPermissionAllowlistPackages: IndexedListSet<String>,
-    var permissionAllowlist: PermissionAllowlist,
-    var implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>,
-    var isSystemReady: Boolean,
+private typealias UserIdsReference = MutableReference<IntSet, MutableIntSet>
+
+typealias AppIdPackageNames = IntReferenceMap<IndexedListSet<String>, MutableIndexedListSet<String>>
+typealias MutableAppIdPackageNames =
+    MutableIntReferenceMap<IndexedListSet<String>, MutableIndexedListSet<String>>
+private typealias AppIdPackageNamesReference =
+    MutableReference<AppIdPackageNames, MutableAppIdPackageNames>
+
+private typealias PermissionGroupsReference = MutableReference<
+    IndexedMap<String, PermissionGroupInfo>, MutableIndexedMap<String, PermissionGroupInfo>
+>
+
+private typealias PermissionTreesReference =
+    MutableReference<IndexedMap<String, Permission>, MutableIndexedMap<String, Permission>>
+
+private typealias PermissionsReference =
+    MutableReference<IndexedMap<String, Permission>, MutableIndexedMap<String, Permission>>
+
+sealed class SystemState(
+    val userIdsReference: UserIdsReference,
+    packageStates: Map<String, PackageState>,
+    disabledSystemPackageStates: Map<String, PackageState>,
+    val appIdPackageNamesReference: AppIdPackageNamesReference,
+    knownPackages: IntMap<Array<String>>,
+    isLeanback: Boolean,
+    configPermissions: Map<String, SystemConfig.PermissionEntry>,
+    privilegedPermissionAllowlistPackages: IndexedListSet<String>,
+    permissionAllowlist: PermissionAllowlist,
+    implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>,
+    isSystemReady: Boolean,
     // TODO: Get and watch the state for deviceAndProfileOwners
-    // Mapping from user ID to package name.
-    var deviceAndProfileOwners: IntMap<String>,
-    val permissionGroups: IndexedMap<String, PermissionGroupInfo>,
-    val permissionTrees: IndexedMap<String, Permission>,
-    val permissions: IndexedMap<String, Permission>
-) : WritableState() {
-    constructor() : this(
-        IntSet(),
-        emptyMap(),
-        emptyMap(),
-        IntMap(),
-        IntMap(),
-        false,
-        emptyMap(),
-        IndexedListSet(),
-        PermissionAllowlist(),
-        IndexedMap(),
-        false,
-        IntMap(),
-        IndexedMap(),
-        IndexedMap(),
-        IndexedMap()
-    )
+    deviceAndProfileOwners: IntMap<String>,
+    val permissionGroupsReference: PermissionGroupsReference,
+    val permissionTreesReference: PermissionTreesReference,
+    val permissionsReference: PermissionsReference,
+    writeMode: Int
+) : WritableState, Immutable<MutableSystemState> {
+    val userIds: IntSet
+        get() = userIdsReference.get()
 
-    fun copy(): SystemState =
-        SystemState(
-            userIds.copy(),
-            packageStates,
-            disabledSystemPackageStates,
-            appIds.copy { it.copy() },
-            knownPackages,
-            isLeanback,
-            configPermissions,
-            privilegedPermissionAllowlistPackages,
-            permissionAllowlist,
-            implicitToSourcePermissions,
-            isSystemReady,
-            deviceAndProfileOwners,
-            permissionGroups.copy { it },
-            permissionTrees.copy { it },
-            permissions.copy { it }
-        )
+    var packageStates: Map<String, PackageState> = packageStates
+        protected set
+
+    var disabledSystemPackageStates: Map<String, PackageState> = disabledSystemPackageStates
+        protected set
+
+    val appIdPackageNames: AppIdPackageNames
+        get() = appIdPackageNamesReference.get()
+
+    var knownPackages: IntMap<Array<String>> = knownPackages
+        protected set
+
+    var isLeanback: Boolean = isLeanback
+        protected set
+
+    var configPermissions: Map<String, SystemConfig.PermissionEntry> = configPermissions
+        protected set
+
+    var privilegedPermissionAllowlistPackages: IndexedListSet<String> =
+        privilegedPermissionAllowlistPackages
+        protected set
+
+    var permissionAllowlist: PermissionAllowlist = permissionAllowlist
+        protected set
+
+    var implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>> =
+        implicitToSourcePermissions
+        protected set
+
+    var isSystemReady: Boolean = isSystemReady
+        protected set
+
+    var deviceAndProfileOwners: IntMap<String> = deviceAndProfileOwners
+        protected set
+
+    val permissionGroups: IndexedMap<String, PermissionGroupInfo>
+        get() = permissionGroupsReference.get()
+
+    val permissionTrees: IndexedMap<String, Permission>
+        get() = permissionTreesReference.get()
+
+    val permissions: IndexedMap<String, Permission>
+        get() = permissionsReference.get()
+
+    override var writeMode: Int = writeMode
+        protected set
+
+    override fun toMutable(): MutableSystemState = MutableSystemState(this)
 }
 
-class UserState private constructor(
-    // packageName -> version, this version is used permissions/app-ops states upgrade.
-    val packageVersions: IndexedMap<String, Int>,
-    // A map of (appId to a map of (permissionName to permissionFlags))
-    val appIdPermissionFlags: IntMap<IndexedMap<String, Int>>,
-    // appId -> opName -> opCode
-    val appIdAppOpModes: IntMap<IndexedMap<String, Int>>,
-    // packageName -> opName -> opCode
-    val packageAppOpModes: IndexedMap<String, IndexedMap<String, Int>>,
-) : WritableState() {
+class MutableSystemState private constructor(
+    userIdsReference: UserIdsReference,
+    packageStates: Map<String, PackageState>,
+    disabledSystemPackageStates: Map<String, PackageState>,
+    appIdPackageNamesReference: AppIdPackageNamesReference,
+    knownPackages: IntMap<Array<String>>,
+    isLeanback: Boolean,
+    configPermissions: Map<String, SystemConfig.PermissionEntry>,
+    privilegedPermissionAllowlistPackages: IndexedListSet<String>,
+    permissionAllowlist: PermissionAllowlist,
+    implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>,
+    isSystemReady: Boolean,
+    deviceAndProfileOwners: IntMap<String>,
+    permissionGroupsReference: PermissionGroupsReference,
+    permissionTreesReference: PermissionTreesReference,
+    permissionsReference: PermissionsReference,
+    writeMode: Int
+) : SystemState(
+    userIdsReference,
+    packageStates,
+    disabledSystemPackageStates,
+    appIdPackageNamesReference,
+    knownPackages,
+    isLeanback,
+    configPermissions,
+    privilegedPermissionAllowlistPackages,
+    permissionAllowlist,
+    implicitToSourcePermissions,
+    isSystemReady,
+    deviceAndProfileOwners,
+    permissionGroupsReference,
+    permissionTreesReference,
+    permissionsReference,
+    writeMode
+), MutableWritableState {
     constructor() : this(
-        IndexedMap(),
-        IntMap(),
-        IntMap(),
-        IndexedMap(),
+        UserIdsReference(MutableIntSet()),
+        emptyMap(),
+        emptyMap(),
+        AppIdPackageNamesReference(MutableAppIdPackageNames()),
+        MutableIntMap(),
+        false,
+        emptyMap(),
+        MutableIndexedListSet(),
+        PermissionAllowlist(),
+        MutableIndexedMap(),
+        false,
+        MutableIntMap(),
+        PermissionGroupsReference(MutableIndexedMap()),
+        PermissionTreesReference(MutableIndexedMap()),
+        PermissionsReference(MutableIndexedMap()),
+        WriteMode.NONE
     )
 
-    fun copy(): UserState = UserState(
-        packageVersions.copy { it },
-        appIdPermissionFlags.copy { it.copy { it } },
-        appIdAppOpModes.copy { it.copy { it } },
-        packageAppOpModes.copy { it.copy { it } },
+    internal constructor(systemState: SystemState) : this(
+        systemState.userIdsReference.toImmutable(),
+        systemState.packageStates,
+        systemState.disabledSystemPackageStates,
+        systemState.appIdPackageNamesReference.toImmutable(),
+        systemState.knownPackages,
+        systemState.isLeanback,
+        systemState.configPermissions,
+        systemState.privilegedPermissionAllowlistPackages,
+        systemState.permissionAllowlist,
+        systemState.implicitToSourcePermissions,
+        systemState.isSystemReady,
+        systemState.deviceAndProfileOwners,
+        systemState.permissionGroupsReference.toImmutable(),
+        systemState.permissionTreesReference.toImmutable(),
+        systemState.permissionsReference.toImmutable(),
+        WriteMode.NONE
     )
+
+    fun mutateUserIds(): MutableIntSet = userIdsReference.mutate()
+
+    @JvmName("setPackageStatesPublic")
+    fun setPackageStates(packageStates: Map<String, PackageState>) {
+        this.packageStates = packageStates
+    }
+
+    @JvmName("setDisabledSystemPackageStatesPublic")
+    fun setDisabledSystemPackageStates(disabledSystemPackageStates: Map<String, PackageState>) {
+        this.disabledSystemPackageStates = disabledSystemPackageStates
+    }
+
+    fun mutateAppIdPackageNames(): MutableAppIdPackageNames = appIdPackageNamesReference.mutate()
+
+    @JvmName("setKnownPackagesPublic")
+    fun setKnownPackages(knownPackages: IntMap<Array<String>>) {
+        this.knownPackages = knownPackages
+    }
+
+    @JvmName("setLeanbackPublic")
+    fun setLeanback(isLeanback: Boolean) {
+        this.isLeanback = isLeanback
+    }
+
+    @JvmName("setConfigPermissionsPublic")
+    fun setConfigPermissions(configPermissions: Map<String, SystemConfig.PermissionEntry>) {
+        this.configPermissions = configPermissions
+    }
+
+    @JvmName("setPrivilegedPermissionAllowlistPackagesPublic")
+    fun setPrivilegedPermissionAllowlistPackages(
+        privilegedPermissionAllowlistPackages: IndexedListSet<String>
+    ) {
+        this.privilegedPermissionAllowlistPackages = privilegedPermissionAllowlistPackages
+    }
+
+    @JvmName("setPermissionAllowlistPublic")
+    fun setPermissionAllowlist(permissionAllowlist: PermissionAllowlist) {
+        this.permissionAllowlist = permissionAllowlist
+    }
+
+    @JvmName("setImplicitToSourcePermissionsPublic")
+    fun setImplicitToSourcePermissions(
+        implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>
+    ) {
+        this.implicitToSourcePermissions = implicitToSourcePermissions
+    }
+
+    @JvmName("setSystemReadyPublic")
+    fun setSystemReady(isSystemReady: Boolean) {
+        this.isSystemReady = isSystemReady
+    }
+
+    @JvmName("setDeviceAndProfileOwnersPublic")
+    fun setDeviceAndProfileOwners(deviceAndProfileOwners: IntMap<String>) {
+        this.deviceAndProfileOwners = deviceAndProfileOwners
+    }
+
+    fun mutatePermissionGroups(): MutableIndexedMap<String, PermissionGroupInfo> =
+        permissionGroupsReference.mutate()
+
+    fun mutatePermissionTrees(): MutableIndexedMap<String, Permission> =
+        permissionTreesReference.mutate()
+
+    fun mutatePermissions(): MutableIndexedMap<String, Permission> =
+        permissionsReference.mutate()
+
+    override fun requestWriteMode(writeMode: Int) {
+        this.writeMode = maxOf(this.writeMode, writeMode)
+    }
+}
+
+private typealias PackageVersionsReference =
+    MutableReference<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+
+typealias AppIdPermissionFlags =
+    IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+typealias MutableAppIdPermissionFlags =
+    MutableIntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+private typealias AppIdPermissionFlagsReference =
+    MutableReference<AppIdPermissionFlags, MutableAppIdPermissionFlags>
+
+typealias AppIdAppOpModes =
+    IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+typealias MutableAppIdAppOpModes =
+    MutableIntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+private typealias AppIdAppOpModesReference =
+    MutableReference<AppIdAppOpModes, MutableAppIdAppOpModes>
+
+typealias PackageAppOpModes =
+    IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+typealias MutablePackageAppOpModes =
+    MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+private typealias PackageAppOpModesReference =
+    MutableReference<PackageAppOpModes, MutablePackageAppOpModes>
+
+sealed class UserState(
+    internal val packageVersionsReference: PackageVersionsReference,
+    internal val appIdPermissionFlagsReference: AppIdPermissionFlagsReference,
+    internal val appIdAppOpModesReference: AppIdAppOpModesReference,
+    internal val packageAppOpModesReference: PackageAppOpModesReference,
+    writeMode: Int
+) : WritableState, Immutable<MutableUserState> {
+    val packageVersions: IndexedMap<String, Int>
+        get() = packageVersionsReference.get()
+
+    val appIdPermissionFlags: AppIdPermissionFlags
+        get() = appIdPermissionFlagsReference.get()
+
+    val appIdAppOpModes: AppIdAppOpModes
+        get() = appIdAppOpModesReference.get()
+
+    val packageAppOpModes: PackageAppOpModes
+        get() = packageAppOpModesReference.get()
+
+    override var writeMode: Int = writeMode
+        protected set
+
+    override fun toMutable(): MutableUserState = MutableUserState(this)
+}
+
+class MutableUserState private constructor(
+    packageVersionsReference: PackageVersionsReference,
+    appIdPermissionFlagsReference: AppIdPermissionFlagsReference,
+    appIdAppOpModesReference: AppIdAppOpModesReference,
+    packageAppOpModesReference: PackageAppOpModesReference,
+    writeMode: Int
+) : UserState(
+    packageVersionsReference,
+    appIdPermissionFlagsReference,
+    appIdAppOpModesReference,
+    packageAppOpModesReference,
+    writeMode
+), MutableWritableState {
+    constructor() : this(
+        PackageVersionsReference(MutableIndexedMap<String, Int>()),
+        AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()),
+        AppIdAppOpModesReference(MutableAppIdAppOpModes()),
+        PackageAppOpModesReference(MutablePackageAppOpModes()),
+        WriteMode.NONE
+    )
+
+    internal constructor(userState: UserState) : this(
+        userState.packageVersionsReference.toImmutable(),
+        userState.appIdPermissionFlagsReference.toImmutable(),
+        userState.appIdAppOpModesReference.toImmutable(),
+        userState.packageAppOpModesReference.toImmutable(),
+        WriteMode.NONE
+    )
+
+    fun mutatePackageVersions(): MutableIndexedMap<String, Int> = packageVersionsReference.mutate()
+
+    fun mutateAppIdPermissionFlags(): MutableAppIdPermissionFlags =
+        appIdPermissionFlagsReference.mutate()
+
+    fun mutateAppIdAppOpModes(): MutableAppIdAppOpModes = appIdAppOpModesReference.mutate()
+
+    fun mutatePackageAppOpModes(): MutablePackageAppOpModes = packageAppOpModesReference.mutate()
+
+    override fun requestWriteMode(writeMode: Int) {
+        this.writeMode = maxOf(this.writeMode, writeMode)
+    }
 }
 
 object WriteMode {
     const val NONE = 0
-    const val SYNC = 1
-    const val ASYNC = 2
+    const val ASYNCHRONOUS = 1
+    const val SYNCHRONOUS = 2
 }
 
-abstract class WritableState {
-    var writeMode: Int = WriteMode.NONE
-        private set
+interface WritableState {
+    val writeMode: Int
+}
 
-    fun requestWrite(sync: Boolean = false) {
-        if (sync) {
-            writeMode = WriteMode.SYNC
-        } else {
-            if (writeMode != WriteMode.SYNC) {
-                writeMode = WriteMode.ASYNC
-            }
-        }
-    }
+interface MutableWritableState : WritableState {
+    fun requestWriteMode(writeMode: Int)
 }
 
 open class GetStateScope(
@@ -148,5 +418,5 @@
 
 class MutateStateScope(
     val oldState: AccessState,
-    val newState: AccessState
+    val newState: MutableAccessState
 ) : GetStateScope(newState)
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt
index 71e4f2a..f89f79b 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt
@@ -16,27 +16,46 @@
 
 package com.android.server.permission.access.appop
 
+import android.os.Process
+import android.util.Log
 import com.android.server.LocalServices
 import com.android.server.appop.AppOpMigrationHelper
-import com.android.server.permission.access.AccessState
-import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.util.PackageVersionMigration
 
 class AppIdAppOpMigration {
-    fun migrateUserState(state: AccessState, userId: Int) {
+    fun migrateUserState(state: MutableAccessState, userId: Int) {
         val legacyAppOpsManager = LocalServices.getService(AppOpMigrationHelper::class.java)!!
         val legacyAppIdAppOpModes = legacyAppOpsManager.getLegacyAppIdAppOpModes(userId)
-        val appIdAppOpModes = state.userStates[userId].appIdAppOpModes
         val version = PackageVersionMigration.getVersion(userId)
+
+        val userState = state.mutateUserState(userId)!!
+        val appIdAppOpModes = userState.mutateAppIdAppOpModes()
         legacyAppIdAppOpModes.forEach { (appId, legacyAppOpModes) ->
-            val appOpModes = appIdAppOpModes.getOrPut(appId) { IndexedMap() }
+            val packageNames = state.systemState.appIdPackageNames[appId]
+            // Non-application UIDs may not have an Android package but may still have app op state.
+            if (packageNames == null && appId >= Process.FIRST_APPLICATION_UID) {
+                Log.w(LOG_TAG, "Dropping unknown app ID $appId when migrating app op state")
+                return@forEach
+            }
+
+            val appOpModes = MutableIndexedMap<String, Int>()
+            appIdAppOpModes[appId] = appOpModes
             legacyAppOpModes.forEach { (appOpName, appOpMode) ->
                 appOpModes[appOpName] = appOpMode
             }
 
-            state.systemState.appIds[appId].forEachIndexed { _, packageName ->
-                state.userStates[userId].packageVersions[packageName] = version
+            if (packageNames != null) {
+                val packageVersions = userState.mutatePackageVersions()
+                packageNames.forEachIndexed { _, packageName ->
+                    packageVersions[packageName] = version
+                }
             }
         }
     }
+
+    companion object {
+        private val LOG_TAG = AppIdAppOpMigration::class.java.simpleName
+    }
 }
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt
index c29f30c8..b6cb17a 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt
@@ -16,12 +16,16 @@
 
 package com.android.server.permission.access.appop
 
+import android.os.Process
 import android.util.Log
 import com.android.modules.utils.BinaryXmlPullParser
 import com.android.modules.utils.BinaryXmlSerializer
 import com.android.server.permission.access.AccessState
-import com.android.server.permission.access.UserState
-import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.AppIdAppOpModes
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.MutableAppIdAppOpModes
+import com.android.server.permission.access.WriteMode
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.util.attributeInt
 import com.android.server.permission.access.util.forEachTag
 import com.android.server.permission.access.util.getAttributeIntOrThrow
@@ -29,44 +33,47 @@
 import com.android.server.permission.access.util.tagName
 
 class AppIdAppOpPersistence : BaseAppOpPersistence() {
-    override fun BinaryXmlPullParser.parseUserState(state: AccessState, userId: Int) {
+    override fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
         when (tagName) {
             TAG_APP_ID_APP_OPS -> parseAppIdAppOps(state, userId)
             else -> {}
         }
     }
 
-    private fun BinaryXmlPullParser.parseAppIdAppOps(state: AccessState, userId: Int) {
-        val userState = state.userStates[userId]
+    private fun BinaryXmlPullParser.parseAppIdAppOps(state: MutableAccessState, userId: Int) {
+        val userState = state.mutateUserState(userId, WriteMode.NONE)!!
+        val appIdAppOpModes = userState.mutateAppIdAppOpModes()
         forEachTag {
             when (tagName) {
-                TAG_APP_ID -> parseAppId(userState)
+                TAG_APP_ID -> parseAppId(appIdAppOpModes)
                 else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
             }
         }
-        userState.appIdAppOpModes.retainAllIndexed { _, appId, _ ->
-            val hasAppId = appId in state.systemState.appIds
-            if (!hasAppId) {
+        userState.appIdAppOpModes.forEachReversedIndexed { appIdIndex, appId, _ ->
+            // Non-application UIDs may not have an Android package but may still have app op state.
+            if (appId !in state.systemState.appIdPackageNames &&
+                appId >= Process.FIRST_APPLICATION_UID) {
                 Log.w(LOG_TAG, "Dropping unknown app ID $appId when parsing app-op state")
+                appIdAppOpModes.removeAt(appIdIndex)
+                userState.requestWriteMode(WriteMode.ASYNCHRONOUS)
             }
-            hasAppId
         }
     }
 
-    private fun BinaryXmlPullParser.parseAppId(userState: UserState) {
+    private fun BinaryXmlPullParser.parseAppId(appIdAppOpModes: MutableAppIdAppOpModes) {
         val appId = getAttributeIntOrThrow(ATTR_ID)
-        val appOpModes = IndexedMap<String, Int>()
-        userState.appIdAppOpModes[appId] = appOpModes
+        val appOpModes = MutableIndexedMap<String, Int>()
+        appIdAppOpModes[appId] = appOpModes
         parseAppOps(appOpModes)
     }
 
     override fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
-        serializeAppIdAppOps(state.userStates[userId])
+        serializeAppIdAppOps(state.userStates[userId]!!.appIdAppOpModes)
     }
 
-    private fun BinaryXmlSerializer.serializeAppIdAppOps(userState: UserState) {
+    private fun BinaryXmlSerializer.serializeAppIdAppOps(appIdAppOpModes: AppIdAppOpModes) {
         tag(TAG_APP_ID_APP_OPS) {
-            userState.appIdAppOpModes.forEachIndexed { _, appId, appOpModes ->
+            appIdAppOpModes.forEachIndexed { _, appId, appOpModes ->
                 serializeAppId(appId, appOpModes)
             }
         }
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
index bd94955..d783fc8 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
@@ -17,13 +17,14 @@
 package com.android.server.permission.access.appop
 
 import android.app.AppOpsManager
-import com.android.server.permission.access.AccessState
 import com.android.server.permission.access.AccessUri
 import com.android.server.permission.access.AppOpUri
 import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutableAccessState
 import com.android.server.permission.access.MutateStateScope
 import com.android.server.permission.access.UidUri
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.pm.pkg.PackageState
 
 class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) {
@@ -32,7 +33,8 @@
     private val upgrade = AppIdAppOpUpgrade(this)
 
     @Volatile
-    private var onAppOpModeChangedListeners = IndexedListSet<OnAppOpModeChangedListener>()
+    private var onAppOpModeChangedListeners: IndexedListSet<OnAppOpModeChangedListener> =
+        MutableIndexedListSet()
     private val onAppOpModeChangedListenersLock = Any()
 
     override val subjectScheme: String
@@ -59,27 +61,30 @@
     }
 
     override fun MutateStateScope.onAppIdRemoved(appId: Int) {
-        newState.userStates.forEachIndexed { _, _, userState ->
-            userState.appIdAppOpModes -= appId
-            userState.requestWrite()
-            // Skip notifying the change listeners since the app ID no longer exists.
+        newState.userStates.forEachIndexed { userStateIndex, _, userState ->
+            val appIdIndex = userState.appIdAppOpModes.indexOfKey(appId)
+            if (appIdIndex >= 0) {
+                newState.mutateUserStateAt(userStateIndex).mutateAppIdAppOpModes()
+                    .removeAt(appIdIndex)
+                // Skip notifying the change listeners since the app ID no longer exists.
+            }
         }
     }
 
     fun GetStateScope.getAppOpModes(appId: Int, userId: Int): IndexedMap<String, Int>? =
-        state.userStates[userId].appIdAppOpModes[appId]
+        state.userStates[userId]!!.appIdAppOpModes[appId]
 
     fun MutateStateScope.removeAppOpModes(appId: Int, userId: Int): Boolean {
-        val userState = newState.userStates[userId]
-        val isChanged = userState.appIdAppOpModes.removeReturnOld(appId) != null
-        if (isChanged) {
-            userState.requestWrite()
+        val appIdIndex = newState.userStates[userId]!!.appIdAppOpModes.indexOfKey(appId)
+        if (appIdIndex < 0) {
+            return false
         }
-        return isChanged
+        newState.mutateUserState(userId)!!.mutateAppIdAppOpModes().removeAt(appIdIndex)
+        return true
     }
 
     fun GetStateScope.getAppOpMode(appId: Int, userId: Int, appOpName: String): Int =
-        state.userStates[userId].appIdAppOpModes[appId]
+        state.userStates[userId]!!.appIdAppOpModes[appId]
             .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName))
 
     fun MutateStateScope.setAppOpMode(
@@ -88,23 +93,18 @@
         appOpName: String,
         mode: Int
     ): Boolean {
-        val userState = newState.userStates[userId]
-        val appIdAppOpModes = userState.appIdAppOpModes
-        var appOpModes = appIdAppOpModes[appId]
         val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
-        val oldMode = appOpModes.getWithDefault(appOpName, defaultMode)
+        val oldMode = newState.userStates[userId]!!.appIdAppOpModes[appId]
+            .getWithDefault(appOpName, defaultMode)
         if (oldMode == mode) {
             return false
         }
-        if (appOpModes == null) {
-            appOpModes = IndexedMap()
-            appIdAppOpModes[appId] = appOpModes
-        }
+        val appIdAppOpModes = newState.mutateUserState(userId)!!.mutateAppIdAppOpModes()
+        val appOpModes = appIdAppOpModes.mutateOrPut(appId) { MutableIndexedMap() }
         appOpModes.putWithDefault(appOpName, mode, defaultMode)
         if (appOpModes.isEmpty()) {
             appIdAppOpModes -= appId
         }
-        userState.requestWrite()
         onAppOpModeChangedListeners.forEachIndexed { _, it ->
             it.onAppOpModeChanged(appId, userId, appOpName, oldMode, mode)
         }
@@ -123,7 +123,7 @@
         }
     }
 
-    override fun migrateUserState(state: AccessState, userId: Int) {
+    override fun migrateUserState(state: MutableAccessState, userId: Int) {
         with(migration) { migrateUserState(state, userId) }
     }
 
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index 17c92ac..f389826 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -19,6 +19,9 @@
 import android.app.AppOpsManager
 import android.os.Handler
 import android.os.UserHandle
+import android.util.ArrayMap
+import android.util.SparseArray
+import android.util.SparseBooleanArray
 import android.util.SparseIntArray
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.util.ArrayUtils
@@ -27,10 +30,7 @@
 import com.android.server.permission.access.AppOpUri
 import com.android.server.permission.access.PackageUri
 import com.android.server.permission.access.UidUri
-import com.android.server.permission.access.collection.IndexedMap
-import com.android.server.permission.access.collection.IntBooleanMap
-import com.android.server.permission.access.collection.IntMap
-import com.android.server.permission.access.collection.forEachIndexed
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
 
 class AppOpService(
     private val service: AccessCheckingService
@@ -43,7 +43,7 @@
     private val context = service.context
     private lateinit var handler: Handler
     private lateinit var lock: Any
-    private lateinit var switchedOps: IntMap<IntArray>
+    private lateinit var switchedOps: SparseArray<IntArray>
 
     fun initialize() {
         // TODO(b/252883039): Wrong handler. Inject main thread handler here.
@@ -51,7 +51,7 @@
         // TODO(b/252883039): Wrong lock object. Inject AppOpsService here.
         lock = Any()
 
-        switchedOps = IntMap()
+        switchedOps = SparseArray()
         for (switchedCode in 0 until AppOpsManager._NUM_OP) {
             val switchCode = AppOpsManager.opToSwitch(switchedCode)
             switchedOps.put(switchCode,
@@ -78,11 +78,11 @@
     }
 
     override fun getNonDefaultUidModes(uid: Int): SparseIntArray {
-        return opNameMapToOpIntMap(getUidModes(uid))
+        return opNameMapToOpSparseArray(getUidModes(uid))
     }
 
     override fun getNonDefaultPackageModes(packageName: String, userId: Int): SparseIntArray {
-        return opNameMapToOpIntMap(getPackageModes(packageName, userId))
+        return opNameMapToOpSparseArray(getPackageModes(packageName, userId))
     }
 
     override fun getUidMode(uid: Int, op: Int): Int {
@@ -94,12 +94,12 @@
         }
     }
 
-    private fun getUidModes(uid: Int): IndexedMap<String, Int>? {
+    private fun getUidModes(uid: Int): ArrayMap<String, Int>? {
         val appId = UserHandle.getAppId(uid)
         val userId = UserHandle.getUserId(uid)
         return service.getState {
             with(uidPolicy) { getAppOpModes(appId, userId) }
-        }
+        }?.map
     }
 
     override fun setUidMode(uid: Int, op: Int, mode: Int): Boolean {
@@ -123,8 +123,8 @@
     private fun getPackageModes(
         packageName: String,
         userId: Int
-    ): IndexedMap<String, Int>? =
-        service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }
+    ): ArrayMap<String, Int>? =
+        service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }?.map
 
     override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) {
         val opName = AppOpsManager.opToPublicName(op)
@@ -149,15 +149,15 @@
         return wasChanged
     }
 
-    private fun opNameMapToOpIntMap(modes: IndexedMap<String, Int>?): SparseIntArray =
+    private fun opNameMapToOpSparseArray(modes: ArrayMap<String, Int>?): SparseIntArray =
         if (modes == null) {
             SparseIntArray()
         } else {
-            val opIntMap = SparseIntArray(modes.size)
+            val opSparseArray = SparseIntArray(modes.size)
             modes.forEachIndexed { _, opName, opMode ->
-                opIntMap.put(AppOpsManager.strOpToOp(opName), opMode)
+                opSparseArray.put(AppOpsManager.strOpToOp(opName), opMode)
             }
-            opIntMap
+            opSparseArray
         }
 
     override fun areUidModesDefault(uid: Int): Boolean {
@@ -175,21 +175,21 @@
         // and we have our own persistence.
     }
 
-    override fun getForegroundOps(uid: Int): IntBooleanMap {
-        return IntBooleanMap().apply {
-            getUidModes(uid)?.forEachIndexed { _, code, mode ->
+    override fun getForegroundOps(uid: Int): SparseBooleanArray {
+        return SparseBooleanArray().apply {
+            getUidModes(uid)?.forEachIndexed { _, op, mode ->
                 if (mode == AppOpsManager.MODE_FOREGROUND) {
-                    put(AppOpsManager.strOpToOp(code), true)
+                    this[AppOpsManager.strOpToOp(op)] = true
                 }
             }
         }
     }
 
-    override fun getForegroundOps(packageName: String, userId: Int): IntBooleanMap {
-        return IntBooleanMap().apply {
-            getPackageModes(packageName, userId)?.forEachIndexed { _, code, mode ->
+    override fun getForegroundOps(packageName: String, userId: Int): SparseBooleanArray {
+        return SparseBooleanArray().apply {
+            getPackageModes(packageName, userId)?.forEachIndexed { _, op, mode ->
                 if (mode == AppOpsManager.MODE_FOREGROUND) {
-                    put(AppOpsManager.strOpToOp(code), true)
+                    this[AppOpsManager.strOpToOp(op)] = true
                 }
             }
         }
diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt
index 5faf96f..53e5392 100644
--- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt
@@ -20,7 +20,9 @@
 import com.android.modules.utils.BinaryXmlPullParser
 import com.android.modules.utils.BinaryXmlSerializer
 import com.android.server.permission.access.AccessState
+import com.android.server.permission.access.MutableAccessState
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.util.attributeInt
 import com.android.server.permission.access.util.attributeInterned
 import com.android.server.permission.access.util.forEachTag
@@ -30,11 +32,11 @@
 import com.android.server.permission.access.util.tagName
 
 abstract class BaseAppOpPersistence {
-    abstract fun BinaryXmlPullParser.parseUserState(state: AccessState, userId: Int)
+    abstract fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int)
 
     abstract fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int)
 
-    protected fun BinaryXmlPullParser.parseAppOps(appOpModes: IndexedMap<String, Int>) {
+    protected fun BinaryXmlPullParser.parseAppOps(appOpModes: MutableIndexedMap<String, Int>) {
         forEachTag {
             when (tagName) {
                 TAG_APP_OP -> parseAppOp(appOpModes)
@@ -43,7 +45,7 @@
         }
     }
 
-    private fun BinaryXmlPullParser.parseAppOp(appOpModes: IndexedMap<String, Int>) {
+    private fun BinaryXmlPullParser.parseAppOp(appOpModes: MutableIndexedMap<String, Int>) {
         val name = getAttributeValueOrThrow(ATTR_NAME).intern()
         val mode = getAttributeIntOrThrow(ATTR_MODE)
         appOpModes[name] = mode
diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
index 9c8c0ce..c0a85f8 100644
--- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
@@ -20,6 +20,7 @@
 import com.android.modules.utils.BinaryXmlSerializer
 import com.android.server.permission.access.AccessState
 import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.MutableAccessState
 import com.android.server.permission.access.SchemePolicy
 
 abstract class BaseAppOpPolicy(
@@ -28,7 +29,7 @@
     override val objectScheme: String
         get() = AppOpUri.SCHEME
 
-    override fun BinaryXmlPullParser.parseUserState(state: AccessState, userId: Int) {
+    override fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
         with(persistence) { this@parseUserState.parseUserState(state, userId) }
     }
 
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt
index c9651b2..9a6ca82 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt
@@ -16,24 +16,39 @@
 
 package com.android.server.permission.access.appop
 
+import android.util.Log
 import com.android.server.LocalServices
 import com.android.server.appop.AppOpMigrationHelper
-import com.android.server.permission.access.AccessState
+import com.android.server.permission.access.MutableAccessState
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.util.PackageVersionMigration
 
 class PackageAppOpMigration {
-    fun migrateUserState(state: AccessState, userId: Int) {
+    fun migrateUserState(state: MutableAccessState, userId: Int) {
         val legacyAppOpsManager = LocalServices.getService(AppOpMigrationHelper::class.java)!!
         val legacyPackageAppOpModes = legacyAppOpsManager.getLegacyPackageAppOpModes(userId)
-        val packageAppOpModes = state.userStates[userId].packageAppOpModes
         val version = PackageVersionMigration.getVersion(userId)
+
+        val userState = state.mutateUserState(userId)!!
+        val packageAppOpModes = userState.mutatePackageAppOpModes()
         legacyPackageAppOpModes.forEach { (packageName, legacyAppOpModes) ->
-            val appOpModes = packageAppOpModes.getOrPut(packageName) { IndexedMap() }
+            if (packageName !in state.systemState.packageStates) {
+                Log.w(LOG_TAG, "Dropping unknown package $packageName when migrating app op state")
+                return@forEach
+            }
+
+            val appOpModes = MutableIndexedMap<String, Int>()
+            packageAppOpModes[packageName] = appOpModes
             legacyAppOpModes.forEach { (appOpName, appOpMode) ->
                 appOpModes[appOpName] = appOpMode
             }
-            state.userStates[userId].packageVersions[packageName] = version
+
+            userState.mutatePackageVersions()[packageName] = version
         }
     }
+
+    companion object {
+        private val LOG_TAG = PackageAppOpMigration::class.java.simpleName
+    }
 }
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt
index 6ef117a..b424b4e 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt
@@ -20,8 +20,12 @@
 import com.android.modules.utils.BinaryXmlPullParser
 import com.android.modules.utils.BinaryXmlSerializer
 import com.android.server.permission.access.AccessState
-import com.android.server.permission.access.UserState
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.MutablePackageAppOpModes
+import com.android.server.permission.access.PackageAppOpModes
+import com.android.server.permission.access.WriteMode
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.util.attributeInterned
 import com.android.server.permission.access.util.forEachTag
 import com.android.server.permission.access.util.getAttributeValueOrThrow
@@ -29,44 +33,45 @@
 import com.android.server.permission.access.util.tagName
 
 class PackageAppOpPersistence : BaseAppOpPersistence() {
-    override fun BinaryXmlPullParser.parseUserState(state: AccessState, userId: Int) {
+    override fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
         when (tagName) {
             TAG_PACKAGE_APP_OPS -> parsePackageAppOps(state, userId)
             else -> {}
         }
     }
 
-    private fun BinaryXmlPullParser.parsePackageAppOps(state: AccessState, userId: Int) {
-        val userState = state.userStates[userId]
+    private fun BinaryXmlPullParser.parsePackageAppOps(state: MutableAccessState, userId: Int) {
+        val userState = state.mutateUserState(userId, WriteMode.NONE)!!
+        val packageAppOpModes = userState.mutatePackageAppOpModes()
         forEachTag {
             when (tagName) {
-                TAG_PACKAGE -> parsePackage(userState)
+                TAG_PACKAGE -> parsePackage(packageAppOpModes)
                 else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
             }
         }
-        userState.packageAppOpModes.retainAllIndexed { _, packageName, _ ->
-            val hasPackage = packageName in state.systemState.packageStates
-            if (!hasPackage) {
+        packageAppOpModes.forEachReversedIndexed { packageNameIndex, packageName, _ ->
+            if (packageName !in state.systemState.packageStates) {
                 Log.w(LOG_TAG, "Dropping unknown package $packageName when parsing app-op state")
+                packageAppOpModes.removeAt(packageNameIndex)
+                userState.requestWriteMode(WriteMode.ASYNCHRONOUS)
             }
-            hasPackage
         }
     }
 
-    private fun BinaryXmlPullParser.parsePackage(userState: UserState) {
+    private fun BinaryXmlPullParser.parsePackage(packageAppOpModes: MutablePackageAppOpModes) {
         val packageName = getAttributeValueOrThrow(ATTR_NAME).intern()
-        val appOpModes = IndexedMap<String, Int>()
-        userState.packageAppOpModes[packageName] = appOpModes
+        val appOpModes = MutableIndexedMap<String, Int>()
+        packageAppOpModes[packageName] = appOpModes
         parseAppOps(appOpModes)
     }
 
     override fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
-        serializePackageAppOps(state.userStates[userId])
+        serializePackageAppOps(state.userStates[userId]!!.packageAppOpModes)
     }
 
-    private fun BinaryXmlSerializer.serializePackageAppOps(userState: UserState) {
+    private fun BinaryXmlSerializer.serializePackageAppOps(packageAppOpModes: PackageAppOpModes) {
         tag(TAG_PACKAGE_APP_OPS) {
-            userState.packageAppOpModes.forEachIndexed { _, packageName, appOpModes ->
+            packageAppOpModes.forEachIndexed { _, packageName, appOpModes ->
                 serializePackage(packageName, appOpModes)
             }
         }
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index b4c4796..bd0713c 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -17,13 +17,14 @@
 package com.android.server.permission.access.appop
 
 import android.app.AppOpsManager
-import com.android.server.permission.access.AccessState
 import com.android.server.permission.access.AccessUri
 import com.android.server.permission.access.AppOpUri
 import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutableAccessState
 import com.android.server.permission.access.MutateStateScope
 import com.android.server.permission.access.PackageUri
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.pm.pkg.PackageState
 
 class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
@@ -32,7 +33,8 @@
     private val upgrade = PackageAppOpUpgrade(this)
 
     @Volatile
-    private var onAppOpModeChangedListeners = IndexedListSet<OnAppOpModeChangedListener>()
+    private var onAppOpModeChangedListeners: IndexedListSet<OnAppOpModeChangedListener> =
+        MutableIndexedListSet()
     private val onAppOpModeChangedListenersLock = Any()
 
     override val subjectScheme: String
@@ -59,27 +61,31 @@
     }
 
     override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
-        newState.userStates.forEachIndexed { _, _, userState ->
-            userState.packageAppOpModes -= packageName
-            userState.requestWrite()
-            // Skip notifying the change listeners since the package no longer exists.
+        newState.userStates.forEachIndexed { userStateIndex, _, userState ->
+            val packageNameIndex = userState.packageAppOpModes.indexOfKey(packageName)
+            if (packageNameIndex >= 0) {
+                newState.mutateUserStateAt(userStateIndex).mutatePackageAppOpModes()
+                    .removeAt(packageNameIndex)
+                // Skip notifying the change listeners since the package no longer exists.
+            }
         }
     }
 
     fun GetStateScope.getAppOpModes(packageName: String, userId: Int): IndexedMap<String, Int>? =
-        state.userStates[userId].packageAppOpModes[packageName]
+        state.userStates[userId]!!.packageAppOpModes[packageName]
 
     fun MutateStateScope.removeAppOpModes(packageName: String, userId: Int): Boolean {
-        val userState = newState.userStates[userId]
-        val isChanged = userState.packageAppOpModes.remove(packageName) != null
-        if (isChanged) {
-            userState.requestWrite()
+        val packageNameIndex = newState.userStates[userId]!!.packageAppOpModes
+            .indexOfKey(packageName)
+        if (packageNameIndex < 0) {
+            return false
         }
-        return isChanged
+        newState.mutateUserState(userId)!!.mutatePackageAppOpModes().removeAt(packageNameIndex)
+        return true
     }
 
     fun GetStateScope.getAppOpMode(packageName: String, userId: Int, appOpName: String): Int =
-        state.userStates[userId].packageAppOpModes[packageName]
+        state.userStates[userId]!!.packageAppOpModes[packageName]
             .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName))
 
     fun MutateStateScope.setAppOpMode(
@@ -88,23 +94,18 @@
         appOpName: String,
         mode: Int
     ): Boolean {
-        val userState = newState.userStates[userId]
-        val packageAppOpModes = userState.packageAppOpModes
-        var appOpModes = packageAppOpModes[packageName]
         val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
-        val oldMode = appOpModes.getWithDefault(appOpName, defaultMode)
+        val oldMode = newState.userStates[userId]!!.packageAppOpModes[packageName]
+            .getWithDefault(appOpName, defaultMode)
         if (oldMode == mode) {
             return false
         }
-        if (appOpModes == null) {
-            appOpModes = IndexedMap()
-            packageAppOpModes[packageName] = appOpModes
-        }
+        val packageAppOpModes = newState.mutateUserState(userId)!!.mutatePackageAppOpModes()
+        val appOpModes = packageAppOpModes.mutateOrPut(packageName) { MutableIndexedMap() }
         appOpModes.putWithDefault(appOpName, mode, defaultMode)
         if (appOpModes.isEmpty()) {
             packageAppOpModes -= packageName
         }
-        userState.requestWrite()
         onAppOpModeChangedListeners.forEachIndexed { _, it ->
             it.onAppOpModeChanged(packageName, userId, appOpName, oldMode, mode)
         }
@@ -123,7 +124,7 @@
         }
     }
 
-    override fun migrateUserState(state: AccessState, userId: Int) {
+    override fun migrateUserState(state: MutableAccessState, userId: Int) {
         with(migration) { migrateUserState(state, userId) }
     }
 
diff --git a/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt
new file mode 100644
index 0000000..686db42
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.collection
+
+import android.util.ArrayMap
+
+inline fun <K, V> ArrayMap<K, V>.allIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <K, V> ArrayMap<K, V>.anyIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <K, V> ArrayMap<K, V>.forEachIndexed(action: (Int, K, V) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <K, V> ArrayMap<K, V>.forEachReversedIndexed(action: (Int, K, V) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <K, V> ArrayMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V {
+    get(key)?.let { return it }
+    return defaultValue().also { put(key, it) }
+}
+
+inline val <K, V> ArrayMap<K, V>.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <K, V> ArrayMap<K, V>.minusAssign(key: K) {
+    remove(key)
+}
+
+inline fun <K, V> ArrayMap<K, V>.noneIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <K, V> ArrayMap<K, V>.removeAllIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+inline fun <K, V> ArrayMap<K, V>.retainAllIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <K, V> ArrayMap<K, V>.set(key: K, value: V) {
+    put(key, value)
+}
diff --git a/services/permission/java/com/android/server/permission/access/collection/ArraySetExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/ArraySetExtensions.kt
new file mode 100644
index 0000000..4710103
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/ArraySetExtensions.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.collection
+
+import android.util.ArraySet
+
+fun <T> arraySetOf(vararg elements: T): ArraySet<T> = ArraySet(elements.asList())
+
+inline fun <T> ArraySet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, value ->
+        if (!predicate(index, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> ArraySet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, value ->
+        if (predicate(index, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <T> ArraySet<T>.forEachIndexed(action: (Int, T) -> Unit) {
+    for (index in 0 until size) {
+        action(index, valueAt(index))
+    }
+}
+
+inline fun <T> ArraySet<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, valueAt(index))
+    }
+}
+
+inline val <T> ArraySet<T>.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> ArraySet<T>.minusAssign(value: T) {
+    remove(value)
+}
+
+inline fun <T> ArraySet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, value ->
+        if (predicate(index, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> ArraySet<T>.plusAssign(value: T) {
+    add(value)
+}
+
+inline fun <T> ArraySet<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, value ->
+        if (predicate(index, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+inline fun <T> ArraySet<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, value ->
+        if (!predicate(index, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
deleted file mode 100644
index f4ecceb..0000000
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
+++ /dev/null
@@ -1,108 +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.permission.access.collection
-
-typealias IndexedList<T> = ArrayList<T>
-
-inline fun <T> IndexedList<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (!predicate(index, element)) {
-            return false
-        }
-    }
-    return true
-}
-
-inline fun <T> IndexedList<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (predicate(index, element)) {
-            return true
-        }
-    }
-    return false
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun <T> IndexedList<T>.copy(): IndexedList<T> = IndexedList(this)
-
-inline fun <T> IndexedList<T>.forEachIndexed(action: (Int, T) -> Unit) {
-    for (index in indices) {
-        action(index, this[index])
-    }
-}
-
-inline fun <T> IndexedList<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
-    for (index in lastIndex downTo 0) {
-        action(index, this[index])
-    }
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedList<T>.minus(element: T): IndexedList<T> =
-    copy().apply { this -= element }
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedList<T>.minusAssign(element: T) {
-    remove(element)
-}
-
-inline fun <T> IndexedList<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (predicate(index, element)) {
-            return false
-        }
-    }
-    return true
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedList<T>.plus(element: T): IndexedList<T> =
-    copy().apply { this += element }
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedList<T>.plusAssign(element: T) {
-    add(element)
-}
-
-inline fun <T> IndexedList<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, element ->
-        if (predicate(index, element)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun <T> IndexedList<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, element ->
-        if (!predicate(index, element)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun <T, R> IndexedList<T>.mapNotNullIndexed(transform: (T) -> R?): IndexedList<R> =
-    IndexedList<R>().also { destination ->
-        forEachIndexed { _, element ->
-            transform(element)?.let { destination += it }
-        }
-    }
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt
deleted file mode 100644
index c40f7ee..0000000
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt
+++ /dev/null
@@ -1,152 +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.permission.access.collection
-
-class IndexedListSet<T> private constructor(
-    private val list: ArrayList<T>
-) : MutableSet<T> {
-    constructor() : this(ArrayList())
-
-    override val size: Int
-        get() = list.size
-
-    override fun contains(element: T): Boolean = list.contains(element)
-
-    override fun isEmpty(): Boolean = list.isEmpty()
-
-    override fun iterator(): MutableIterator<T> = list.iterator()
-
-    override fun containsAll(elements: Collection<T>): Boolean {
-        throw NotImplementedError()
-    }
-
-    fun elementAt(index: Int): T = list[index]
-
-    fun indexOf(element: T): Int = list.indexOf(element)
-
-    override fun add(element: T): Boolean =
-        if (list.contains(element)) {
-            false
-        } else {
-            list.add(element)
-            true
-        }
-
-    override fun remove(element: T): Boolean = list.remove(element)
-
-    override fun clear() {
-        list.clear()
-    }
-
-    override fun addAll(elements: Collection<T>): Boolean {
-        throw NotImplementedError()
-    }
-
-    override fun removeAll(elements: Collection<T>): Boolean {
-        throw NotImplementedError()
-    }
-
-    override fun retainAll(elements: Collection<T>): Boolean {
-        throw NotImplementedError()
-    }
-
-    fun removeAt(index: Int): T? = list.removeAt(index)
-
-    fun copy(): IndexedListSet<T> = IndexedListSet(ArrayList(list))
-}
-
-inline fun <T> IndexedListSet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (!predicate(index, element)) {
-            return false
-        }
-    }
-    return true
-}
-
-inline fun <T> IndexedListSet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (predicate(index, element)) {
-            return true
-        }
-    }
-    return false
-}
-
-inline fun <T> IndexedListSet<T>.forEachIndexed(action: (Int, T) -> Unit) {
-    for (index in indices) {
-        action(index, elementAt(index))
-    }
-}
-
-inline fun <T> IndexedListSet<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
-    for (index in lastIndex downTo 0) {
-        action(index, elementAt(index))
-    }
-}
-
-inline val <T> IndexedListSet<T>.lastIndex: Int
-    get() = size - 1
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedListSet<T>.minus(element: T): IndexedListSet<T> =
-    copy().apply { this -= element }
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedListSet<T>.minusAssign(element: T) {
-    remove(element)
-}
-
-inline fun <T> IndexedListSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (predicate(index, element)) {
-            return false
-        }
-    }
-    return true
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedListSet<T>.plus(element: T): IndexedListSet<T> =
-    copy().apply { this += element }
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedListSet<T>.plusAssign(element: T) {
-    add(element)
-}
-
-inline fun <T> IndexedListSet<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, element ->
-        if (predicate(index, element)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun <T> IndexedListSet<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, element ->
-        if (!predicate(index, element)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
deleted file mode 100644
index 998d206..0000000
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
+++ /dev/null
@@ -1,179 +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.permission.access.collection
-
-import android.util.ArrayMap
-
-typealias IndexedMap<K, V> = ArrayMap<K, V>
-
-inline fun <K, V> IndexedMap<K, V>.allIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (!predicate(index, key, value)) {
-            return false
-        }
-    }
-    return true
-}
-
-inline fun <K, V> IndexedMap<K, V>.anyIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            return true
-        }
-    }
-    return false
-}
-
-inline fun <K, V> IndexedMap<K, V>.copy(copyValue: (V) -> V): IndexedMap<K, V> =
-    IndexedMap(this).apply {
-        forEachValueIndexed { index, value ->
-            setValueAt(index, copyValue(value))
-        }
-    }
-
-inline fun <K, V, R> IndexedMap<K, V>.firstNotNullOfOrNullIndexed(transform: (Int, K, V) -> R): R? {
-    forEachIndexed { index, key, value ->
-        transform(index, key, value)?.let { return it }
-    }
-    return null
-}
-
-inline fun <K, V> IndexedMap<K, V>.forEachIndexed(action: (Int, K, V) -> Unit) {
-    for (index in 0 until size) {
-        action(index, keyAt(index), valueAt(index))
-    }
-}
-
-inline fun <K, V> IndexedMap<K, V>.forEachKeyIndexed(action: (Int, K) -> Unit) {
-    for (index in 0 until size) {
-        action(index, keyAt(index))
-    }
-}
-
-inline fun <K, V> IndexedMap<K, V>.forEachReversedIndexed(action: (Int, K, V) -> Unit) {
-    for (index in lastIndex downTo 0) {
-        action(index, keyAt(index), valueAt(index))
-    }
-}
-
-inline fun <K, V> IndexedMap<K, V>.forEachValueIndexed(action: (Int, V) -> Unit) {
-    for (index in 0 until size) {
-        action(index, valueAt(index))
-    }
-}
-
-inline fun <K, V> IndexedMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V {
-    get(key)?.let { return it }
-    return defaultValue().also { put(key, it) }
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun <K, V> IndexedMap<K, V>?.getWithDefault(key: K, defaultValue: V): V {
-    this ?: return defaultValue
-    val index = indexOfKey(key)
-    return if (index >= 0) valueAt(index) else defaultValue
-}
-
-inline val <K, V> IndexedMap<K, V>.lastIndex: Int
-    get() = size - 1
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <K, V> IndexedMap<K, V>.minusAssign(key: K) {
-    remove(key)
-}
-
-inline fun <K, V> IndexedMap<K, V>.noneIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            return false
-        }
-    }
-    return true
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun <K, V> IndexedMap<K, V>.putWithDefault(key: K, value: V, defaultValue: V): V {
-    val index = indexOfKey(key)
-    if (index >= 0) {
-        val oldValue = valueAt(index)
-        if (value != oldValue) {
-            if (value == defaultValue) {
-                removeAt(index)
-            } else {
-                setValueAt(index, value)
-            }
-        }
-        return oldValue
-    } else {
-        if (value != defaultValue) {
-            put(key, value)
-        }
-        return defaultValue
-    }
-}
-
-inline fun <K, V> IndexedMap<K, V>.removeAllIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun <K, V> IndexedMap<K, V>.retainAllIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, key, value ->
-        if (!predicate(index, key, value)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun <K, V, R> IndexedMap<K, V>.mapIndexed(transform: (Int, K, V) -> R): IndexedList<R> =
-    IndexedList<R>().also { destination ->
-        forEachIndexed { index, key, value ->
-            transform(index, key, value).let { destination += it }
-        }
-    }
-
-inline fun <K, V, R> IndexedMap<K, V>.mapNotNullIndexed(
-    transform: (Int, K, V) -> R?
-): IndexedList<R> =
-    IndexedList<R>().also { destination ->
-        forEachIndexed { index, key, value ->
-            transform(index, key, value)?.let { destination += it }
-        }
-    }
-
-inline fun <K, V, R> IndexedMap<K, V>.mapNotNullIndexedToSet(
-    transform: (Int, K, V) -> R?
-): IndexedSet<R> =
-    IndexedSet<R>().also { destination ->
-        forEachIndexed { index, key, value ->
-            transform(index, key, value)?.let { destination += it }
-        }
-    }
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <K, V> IndexedMap<K, V>.set(key: K, value: V) {
-    put(key, value)
-}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt
deleted file mode 100644
index 13fa31f..0000000
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt
+++ /dev/null
@@ -1,112 +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.permission.access.collection
-
-import android.util.ArraySet
-
-typealias IndexedSet<T> = ArraySet<T>
-
-inline fun <T> IndexedSet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (!predicate(index, element)) {
-            return false
-        }
-    }
-    return true
-}
-
-inline fun <T> IndexedSet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (predicate(index, element)) {
-            return true
-        }
-    }
-    return false
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun <T> IndexedSet<T>.copy(): IndexedSet<T> = IndexedSet(this)
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun <T> IndexedSet<T>.elementAt(index: Int): T = valueAt(index)
-
-inline fun <T> IndexedSet<T>.forEachIndexed(action: (Int, T) -> Unit) {
-    for (index in indices) {
-        action(index, elementAt(index))
-    }
-}
-
-inline fun <T> IndexedSet<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
-    for (index in lastIndex downTo 0) {
-        action(index, elementAt(index))
-    }
-}
-
-inline val <T> IndexedSet<T>.lastIndex: Int
-    get() = size - 1
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedSet<T>.minus(element: T): IndexedSet<T> =
-    copy().apply { this -= element }
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedSet<T>.minusAssign(element: T) {
-    remove(element)
-}
-
-inline fun <T> IndexedSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (predicate(index, element)) {
-            return false
-        }
-    }
-    return true
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedSet<T>.plus(element: T): IndexedSet<T> =
-    copy().apply { this += element }
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedSet<T>.plusAssign(element: T) {
-    add(element)
-}
-
-inline fun <T> IndexedSet<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, element ->
-        if (predicate(index, element)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun <T> IndexedSet<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, element ->
-        if (!predicate(index, element)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun <T> indexedSetOf(vararg elements: T): IndexedSet<T> = IndexedSet(elements.asList())
diff --git a/services/permission/java/com/android/server/permission/access/collection/IntBooleanMap.kt b/services/permission/java/com/android/server/permission/access/collection/IntBooleanMap.kt
deleted file mode 100644
index 2f7b9bf..0000000
--- a/services/permission/java/com/android/server/permission/access/collection/IntBooleanMap.kt
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.permission.access.collection
-
-import android.util.SparseBooleanArray
-
-typealias IntBooleanMap = SparseBooleanArray
-
-inline fun IntBooleanMap.allIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (!predicate(index, key, value)) {
-            return false
-        }
-    }
-    return true
-}
-
-inline fun IntBooleanMap.anyIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            return true
-        }
-    }
-    return false
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun IntBooleanMap.copy(): IntBooleanMap = clone()
-
-inline fun <R> IntBooleanMap.firstNotNullOfOrNullIndexed(transform: (Int, Int, Boolean) -> R): R? {
-    forEachIndexed { index, key, value ->
-        transform(index, key, value)?.let { return it }
-    }
-    return null
-}
-
-inline fun IntBooleanMap.forEachIndexed(action: (Int, Int, Boolean) -> Unit) {
-    for (index in 0 until size) {
-        action(index, keyAt(index), valueAt(index))
-    }
-}
-
-inline fun IntBooleanMap.forEachKeyIndexed(action: (Int, Int) -> Unit) {
-    for (index in 0 until size) {
-        action(index, keyAt(index))
-    }
-}
-
-inline fun IntBooleanMap.forEachReversedIndexed(action: (Int, Int, Boolean) -> Unit) {
-    for (index in lastIndex downTo 0) {
-        action(index, keyAt(index), valueAt(index))
-    }
-}
-
-inline fun IntBooleanMap.forEachValueIndexed(action: (Int, Boolean) -> Unit) {
-    for (index in 0 until size) {
-        action(index, valueAt(index))
-    }
-}
-
-inline fun IntBooleanMap.getOrPut(key: Int, defaultValue: () -> Boolean): Boolean {
-    val index = indexOfKey(key)
-    return if (index >= 0) {
-        valueAt(index)
-    } else {
-        defaultValue().also { put(key, it) }
-    }
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun IntBooleanMap?.getWithDefault(key: Int, defaultValue: Boolean): Boolean {
-    this ?: return defaultValue
-    return get(key, defaultValue)
-}
-
-inline val IntBooleanMap.lastIndex: Int
-    get() = size - 1
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun IntBooleanMap.minusAssign(key: Int) {
-    delete(key)
-}
-
-inline fun IntBooleanMap.noneIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            return false
-        }
-    }
-    return true
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun IntBooleanMap.putWithDefault(key: Int, value: Boolean, defaultValue: Boolean): Boolean {
-    val index = indexOfKey(key)
-    if (index >= 0) {
-        val oldValue = valueAt(index)
-        if (value != oldValue) {
-            if (value == defaultValue) {
-                removeAt(index)
-            } else {
-                setValueAt(index, value)
-            }
-        }
-        return oldValue
-    } else {
-        if (value != defaultValue) {
-            put(key, value)
-        }
-        return defaultValue
-    }
-}
-
-fun IntBooleanMap.remove(key: Int) {
-    delete(key)
-}
-
-fun IntBooleanMap.remove(key: Int, defaultValue: Boolean): Boolean {
-    val index = indexOfKey(key)
-    return if (index >= 0) {
-        val oldValue = valueAt(index)
-        removeAt(index)
-        oldValue
-    } else {
-        defaultValue
-    }
-}
-
-inline fun IntBooleanMap.removeAllIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun IntBooleanMap.retainAllIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, key, value ->
-        if (!predicate(index, key, value)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun IntBooleanMap.set(key: Int, value: Boolean) {
-    put(key, value)
-}
-
-inline val IntBooleanMap.size: Int
-    get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/collection/IntLongMap.kt b/services/permission/java/com/android/server/permission/access/collection/IntLongMap.kt
deleted file mode 100644
index 692bbd6..0000000
--- a/services/permission/java/com/android/server/permission/access/collection/IntLongMap.kt
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.permission.access.collection
-
-import android.util.SparseLongArray
-
-typealias IntLongMap = SparseLongArray
-
-inline fun IntLongMap.allIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (!predicate(index, key, value)) {
-            return false
-        }
-    }
-    return true
-}
-
-inline fun IntLongMap.anyIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            return true
-        }
-    }
-    return false
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun IntLongMap.copy(): IntLongMap = clone()
-
-inline fun <R> IntLongMap.firstNotNullOfOrNullIndexed(transform: (Int, Int, Long) -> R): R? {
-    forEachIndexed { index, key, value ->
-        transform(index, key, value)?.let { return it }
-    }
-    return null
-}
-
-inline fun IntLongMap.forEachIndexed(action: (Int, Int, Long) -> Unit) {
-    for (index in 0 until size) {
-        action(index, keyAt(index), valueAt(index))
-    }
-}
-
-inline fun IntLongMap.forEachKeyIndexed(action: (Int, Int) -> Unit) {
-    for (index in 0 until size) {
-        action(index, keyAt(index))
-    }
-}
-
-inline fun IntLongMap.forEachReversedIndexed(action: (Int, Int, Long) -> Unit) {
-    for (index in lastIndex downTo 0) {
-        action(index, keyAt(index), valueAt(index))
-    }
-}
-
-inline fun IntLongMap.forEachValueIndexed(action: (Int, Long) -> Unit) {
-    for (index in 0 until size) {
-        action(index, valueAt(index))
-    }
-}
-
-inline fun IntLongMap.getOrPut(key: Int, defaultValue: () -> Long): Long {
-    val index = indexOfKey(key)
-    return if (index >= 0) {
-        valueAt(index)
-    } else {
-        defaultValue().also { put(key, it) }
-    }
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun IntLongMap?.getWithDefault(key: Int, defaultValue: Long): Long {
-    this ?: return defaultValue
-    return get(key, defaultValue)
-}
-
-inline val IntLongMap.lastIndex: Int
-    get() = size - 1
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun IntLongMap.minusAssign(key: Int) {
-    delete(key)
-}
-
-inline fun IntLongMap.noneIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            return false
-        }
-    }
-    return true
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun IntLongMap.putWithDefault(key: Int, value: Long, defaultValue: Long): Long {
-    val index = indexOfKey(key)
-    if (index >= 0) {
-        val oldValue = valueAt(index)
-        if (value != oldValue) {
-            if (value == defaultValue) {
-                removeAt(index)
-            } else {
-                setValueAt(index, value)
-            }
-        }
-        return oldValue
-    } else {
-        if (value != defaultValue) {
-            put(key, value)
-        }
-        return defaultValue
-    }
-}
-
-fun IntLongMap.remove(key: Int) {
-    delete(key)
-}
-
-fun IntLongMap.remove(key: Int, defaultValue: Long): Long {
-    val index = indexOfKey(key)
-    return if (index >= 0) {
-        val oldValue = valueAt(index)
-        removeAt(index)
-        oldValue
-    } else {
-        defaultValue
-    }
-}
-
-inline fun IntLongMap.removeAllIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun IntLongMap.retainAllIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, key, value ->
-        if (!predicate(index, key, value)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun IntLongMap.set(key: Int, value: Long) {
-    put(key, value)
-}
-
-inline val IntLongMap.size: Int
-    get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/collection/IntMap.kt b/services/permission/java/com/android/server/permission/access/collection/IntMap.kt
deleted file mode 100644
index e905567..0000000
--- a/services/permission/java/com/android/server/permission/access/collection/IntMap.kt
+++ /dev/null
@@ -1,164 +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.permission.access.collection
-
-import android.util.SparseArray
-
-typealias IntMap<T> = SparseArray<T>
-
-inline fun <T> IntMap<T>.allIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (!predicate(index, key, value)) {
-            return false
-        }
-    }
-    return true
-}
-
-inline fun <T> IntMap<T>.anyIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            return true
-        }
-    }
-    return false
-}
-
-inline fun <T> IntMap<T>.copy(copyValue: (T) -> T): IntMap<T> =
-    this.clone().apply {
-        forEachValueIndexed { index, value ->
-            setValueAt(index, copyValue(value))
-        }
-    }
-
-inline fun <T, R> IntMap<T>.firstNotNullOfOrNullIndexed(transform: (Int, Int, T) -> R): R? {
-    forEachIndexed { index, key, value ->
-        transform(index, key, value)?.let { return it }
-    }
-    return null
-}
-
-inline fun <T> IntMap<T>.forEachIndexed(action: (Int, Int, T) -> Unit) {
-    for (index in 0 until size) {
-        action(index, keyAt(index), valueAt(index))
-    }
-}
-
-inline fun <T> IntMap<T>.forEachKeyIndexed(action: (Int, Int) -> Unit) {
-    for (index in 0 until size) {
-        action(index, keyAt(index))
-    }
-}
-
-inline fun <T> IntMap<T>.forEachReversedIndexed(action: (Int, Int, T) -> Unit) {
-    for (index in lastIndex downTo 0) {
-        action(index, keyAt(index), valueAt(index))
-    }
-}
-
-inline fun <T> IntMap<T>.forEachValueIndexed(action: (Int, T) -> Unit) {
-    for (index in 0 until size) {
-        action(index, valueAt(index))
-    }
-}
-
-inline fun <T> IntMap<T>.getOrPut(key: Int, defaultValue: () -> T): T {
-    get(key)?.let { return it }
-    return defaultValue().also { put(key, it) }
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun <T> IntMap<T>?.getWithDefault(key: Int, defaultValue: T): T {
-    this ?: return defaultValue
-    val index = indexOfKey(key)
-    return if (index >= 0) valueAt(index) else defaultValue
-}
-
-inline val <T> IntMap<T>.lastIndex: Int
-    get() = size - 1
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IntMap<T>.minusAssign(key: Int) {
-    remove(key)
-}
-
-inline fun <T> IntMap<T>.noneIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            return false
-        }
-    }
-    return true
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun <T> IntMap<T>.putWithDefault(key: Int, value: T, defaultValue: T): T {
-    val index = indexOfKey(key)
-    if (index >= 0) {
-        val oldValue = valueAt(index)
-        if (value != oldValue) {
-            if (value == defaultValue) {
-                removeAt(index)
-            } else {
-                setValueAt(index, value)
-            }
-        }
-        return oldValue
-    } else {
-        if (value != defaultValue) {
-            put(key, value)
-        }
-        return defaultValue
-    }
-}
-
-// SparseArray.removeReturnOld() is @hide, so a backup once we move to APIs.
-fun <T> IntMap<T>.removeReturnOld(key: Int): T? {
-    val index = indexOfKey(key)
-    return if (index >= 0) {
-        val oldValue = valueAt(index)
-        removeAt(index)
-        oldValue
-    } else {
-        null
-    }
-}
-
-inline fun <T> IntMap<T>.removeAllIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun <T> IntMap<T>.retainAllIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, key, value ->
-        if (!predicate(index, key, value)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline val <T> IntMap<T>.size: Int
-    get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/collection/IntSet.kt b/services/permission/java/com/android/server/permission/access/collection/IntSet.kt
deleted file mode 100644
index 4717251..0000000
--- a/services/permission/java/com/android/server/permission/access/collection/IntSet.kt
+++ /dev/null
@@ -1,142 +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.permission.access.collection
-
-import android.util.SparseBooleanArray
-
-class IntSet private constructor(
-    private val array: SparseBooleanArray
-) {
-    constructor() : this(SparseBooleanArray())
-
-    val size: Int
-        get() = array.size()
-
-    operator fun contains(element: Int): Boolean = array[element]
-
-    fun elementAt(index: Int): Int = array.keyAt(index)
-
-    fun indexOf(element: Int): Int = array.indexOfKey(element)
-
-    fun add(element: Int) {
-        array.put(element, true)
-    }
-
-    fun remove(element: Int) {
-        array.delete(element)
-    }
-
-    fun clear() {
-        array.clear()
-    }
-
-    fun removeAt(index: Int) {
-        array.removeAt(index)
-    }
-
-    fun copy(): IntSet = IntSet(array.clone())
-}
-
-fun IntSet(values: IntArray): IntSet = IntSet().apply{ this += values }
-
-inline fun IntSet.allIndexed(predicate: (Int, Int) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (!predicate(index, element)) {
-            return false
-        }
-    }
-    return true
-}
-
-inline fun IntSet.anyIndexed(predicate: (Int, Int) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (predicate(index, element)) {
-            return true
-        }
-    }
-    return false
-}
-
-inline fun IntSet.forEachIndexed(action: (Int, Int) -> Unit) {
-    for (index in 0 until size) {
-        action(index, elementAt(index))
-    }
-}
-
-inline fun IntSet.forEachReversedIndexed(action: (Int, Int) -> Unit) {
-    for (index in lastIndex downTo 0) {
-        action(index, elementAt(index))
-    }
-}
-
-inline val IntSet.lastIndex: Int
-    get() = size - 1
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun IntSet.minus(element: Int): IntSet = copy().apply { this -= element }
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun IntSet.minusAssign(element: Int) {
-    remove(element)
-}
-
-inline fun IntSet.noneIndexed(predicate: (Int, Int) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (predicate(index, element)) {
-            return false
-        }
-    }
-    return true
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun IntSet.plus(element: Int): IntSet = copy().apply { this += element }
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun IntSet.plusAssign(element: Int) {
-    add(element)
-}
-
-operator fun IntSet.plusAssign(set: IntSet) {
-    set.forEachIndexed { _, it -> this += it }
-}
-
-operator fun IntSet.plusAssign(array: IntArray) {
-    array.forEach { this += it }
-}
-
-inline fun IntSet.removeAllIndexed(predicate: (Int, Int) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, element ->
-        if (predicate(index, element)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun IntSet.retainAllIndexed(predicate: (Int, Int) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, element ->
-        if (!predicate(index, element)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
diff --git a/services/permission/java/com/android/server/permission/access/collection/List.kt b/services/permission/java/com/android/server/permission/access/collection/ListExtensions.kt
similarity index 100%
rename from services/permission/java/com/android/server/permission/access/collection/List.kt
rename to services/permission/java/com/android/server/permission/access/collection/ListExtensions.kt
diff --git a/services/permission/java/com/android/server/permission/access/collection/SparseArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/SparseArrayExtensions.kt
new file mode 100644
index 0000000..8b7f3de
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/SparseArrayExtensions.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.collection
+
+import android.util.SparseArray
+
+inline fun <T> SparseArray<T>.allIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> SparseArray<T>.anyIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <T> SparseArray<T>.forEachIndexed(action: (Int, Int, T) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <T> SparseArray<T>.forEachReversedIndexed(action: (Int, Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <T> SparseArray<T>.getOrPut(key: Int, defaultValue: () -> T): T {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        valueAt(index)
+    } else {
+        defaultValue().also { put(key, it) }
+    }
+}
+
+inline val <T> SparseArray<T>.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> SparseArray<T>.minusAssign(key: Int) {
+    delete(key)
+}
+
+inline fun <T> SparseArray<T>.noneIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> SparseArray<T>.removeAllIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+inline fun <T> SparseArray<T>.retainAllIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+inline val <T> SparseArray<T>.size: Int
+    get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/collection/SparseBooleanArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/SparseBooleanArrayExtensions.kt
new file mode 100644
index 0000000..0a4c52b
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/SparseBooleanArrayExtensions.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.collection
+
+import android.util.SparseBooleanArray
+
+inline fun SparseBooleanArray.allIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun SparseBooleanArray.anyIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun SparseBooleanArray.forEachIndexed(action: (Int, Int, Boolean) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun SparseBooleanArray.forEachReversedIndexed(action: (Int, Int, Boolean) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun SparseBooleanArray.getOrPut(key: Int, defaultValue: () -> Boolean): Boolean {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        valueAt(index)
+    } else {
+        defaultValue().also { put(key, it) }
+    }
+}
+
+inline val SparseBooleanArray.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseBooleanArray.minusAssign(key: Int) {
+    delete(key)
+}
+
+inline fun SparseBooleanArray.noneIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+fun SparseBooleanArray.remove(key: Int) {
+    delete(key)
+}
+
+fun SparseBooleanArray.remove(key: Int, defaultValue: Boolean): Boolean {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        val oldValue = valueAt(index)
+        removeAt(index)
+        oldValue
+    } else {
+        defaultValue
+    }
+}
+
+inline fun SparseBooleanArray.removeAllIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+inline fun SparseBooleanArray.retainAllIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseBooleanArray.set(key: Int, value: Boolean) {
+    put(key, value)
+}
+
+inline val SparseBooleanArray.size: Int
+    get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/collection/SparseLongArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/SparseLongArrayExtensions.kt
new file mode 100644
index 0000000..1149c52
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/SparseLongArrayExtensions.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.collection
+
+import android.util.SparseLongArray
+
+inline fun SparseLongArray.allIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun SparseLongArray.anyIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun SparseLongArray.forEachIndexed(action: (Int, Int, Long) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun SparseLongArray.forEachReversedIndexed(action: (Int, Int, Long) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun SparseLongArray.getOrPut(key: Int, defaultValue: () -> Long): Long {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        valueAt(index)
+    } else {
+        defaultValue().also { put(key, it) }
+    }
+}
+
+inline val SparseLongArray.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseLongArray.minusAssign(key: Int) {
+    delete(key)
+}
+
+inline fun SparseLongArray.noneIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+fun SparseLongArray.remove(key: Int) {
+    delete(key)
+}
+
+fun SparseLongArray.remove(key: Int, defaultValue: Long): Long {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        val oldValue = valueAt(index)
+        removeAt(index)
+        oldValue
+    } else {
+        defaultValue
+    }
+}
+
+inline fun SparseLongArray.removeAllIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+inline fun SparseLongArray.retainAllIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseLongArray.set(key: Int, value: Long) {
+    put(key, value)
+}
+
+inline val SparseLongArray.size: Int
+    get() = size()
diff --git a/keystore/java/android/security/GenerateRkpKeyException.java b/services/permission/java/com/android/server/permission/access/immutable/Immutable.kt
similarity index 60%
rename from keystore/java/android/security/GenerateRkpKeyException.java
rename to services/permission/java/com/android/server/permission/access/immutable/Immutable.kt
index a2d65e4..64e6d4d 100644
--- a/keystore/java/android/security/GenerateRkpKeyException.java
+++ b/services/permission/java/com/android/server/permission/access/immutable/Immutable.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 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.
@@ -14,18 +14,8 @@
  * limitations under the License.
  */
 
-package android.security;
+package com.android.server.permission.access.immutable
 
-/**
- * Thrown on problems in attempting to attest to a key using a remotely provisioned key.
- *
- * @hide
- */
-public class GenerateRkpKeyException extends Exception {
-
-    /**
-     * Constructs a new {@code GenerateRkpKeyException}.
-     */
-    public GenerateRkpKeyException() {
-    }
+interface Immutable<M> {
+    fun toMutable(): M
 }
diff --git a/services/permission/java/com/android/server/permission/access/immutable/Immutable.md b/services/permission/java/com/android/server/permission/access/immutable/Immutable.md
new file mode 100644
index 0000000..dcf30d2
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/Immutable.md
@@ -0,0 +1,214 @@
+# Immutable Data Structures
+
+## Introduction
+
+The classes inside this package implements a way to manipulate data in an immutable way, which
+allows achieving lock-free reads for performance-critical code paths, and organizing the
+implementation of complex state transitions in a readable and maintainable way.
+
+## Features
+
+This implementation provides the following features:
+
+- Immutability is implemented leveraging the Java/Kotlin type system.
+
+    Each data structure has both an immutable and a mutable variant, so that the type system will be
+    enforcing proper operations on the data during compilation and preventing any accidental
+    mutations.
+
+- Unmodified portion of the data is shared between mutations.
+
+    Making a full copy of the entire state for any modification is often an overkill and bad for
+    performance, so a path-copy approach is taken when mutating part of the data, which is also
+    enforced by the type system.
+
+- Consecutive modifications can be batched.
+
+    This implementation keeps track of the mutation status of each object and reuses objects that
+    are already copied to perform further mutations, so that temporary copies won't be unnecessarily
+    created.
+
+- No manual `freeze()` calls needed at the end of modifications.
+
+    Thanks to the type system enforced immutability, a mutated data structure can simply be upcasted
+    back to its immutable variant at the end of mutations, so that any future modification will
+    require a new call to `toMutable()` which ensures a new copy is created. This eliminates a whole
+    class of potential issues with a required manual `freeze()` call, which may either be forgotten
+    for (part of) the data and result in hard-to-catch bugs, or require correct boilerplate code
+    that properly propagates this information across the entire tree of objects.
+
+- Android-specific data structures are included.
+
+    Android has its own collection classes (e.g. `ArrayMap` and `SparseArray`) that are preferred
+    (for typical amount of data) for performance reasons, and this implementation provides
+    immutability for them via wrapper classes so that the same underlying implementation is used and
+    the same performance goals are achieved.
+
+- Android Runtime performance is considered.
+
+    Both the immutable and mutable variants are defined as classes and their member methods are
+    final (default in Kotlin), so that the method invocations will be `invoke-direct` and allow
+    better AOT compilation.
+
+    The data structure classes here also deliberately chose to not implement the standard
+    Java/Kotlin collection interfaces, so that we can enforce that a number of standard Java/Kotlin
+    utilities that may be bad for performance or generate interface calls (e.g. Java 8 streams,
+    methods taking non-inlined lambdas and kotlin-stdlib extensions taking interfaces) won't be
+    accidentally used. We will only add utility methods when necessary and with proper performance
+    considerations (e.g. indexed iteration, taking class instead of interface).
+
+## Implementation
+
+### Immutable and mutable classes
+
+In order to leverage the type system to enforce immutability, the core idea is to have both an
+immutable and a mutable class for any data structure, where the latter extends the former
+(important for `MutableReference` later).
+
+### How mutation works
+
+The primary difficulty in design comes when data structures are composed together in a tree-like
+fashion, via map or custom data structures. Specifically, the mutation and copy-on-write would
+first happen on the immediate data structure that is being mutated, which would produce a new
+instance that contains the mutation, however it is the parent data structure that also needs to know
+about this new instance and mutate itself to update its reference to the new child. This problem is
+also referred to as "path copying" in persistent data structures.
+
+This design difficulty is solved by the following convention in this implementation. Normally, the
+immutable class is good for any read-only access. But when any mutations are needed, it can be
+started by calling a `toMutable()` method on the root data structure, which would return a mutable
+class over a shallow copy of the existing data. In order to perform the actual mutation deeper in
+the tree, a chain of `mutateFoo()` calls will be needed to obtain mutable classes of child data
+structures, while these `mutateFoo()` calls are also only available on mutable classes. This way,
+proper chain of mutation is also enforced by the type system, and unmodified data is unchanged and
+reused.
+
+Here is an example of how this convention would work in the real-world. A read access would just
+work as if this implementation isn't involved:
+
+```kotlin
+val permission = state.systemState.permissions[permissionName]
+```
+
+Whereas the write access would remain similar, which is natural and easy-to-use with safety
+guaranteed by the type system:
+
+```kotlin
+val newState = state.toMutable()
+newState.mutateSystemState().mutatePermissions().put(permission.name, permission)
+state = newState
+```
+
+### The magic: `MutableReference`
+
+The magic of the implementation for this convention comes from the `MutableReference` class, and
+below is a simplified version of it.
+
+```kotlin
+class MutableReference<I : Immutable<M>, M : I>(
+    private var immutable: I,
+    private var mutable: M?
+) {
+    fun get(): I = immutable
+
+    fun mutate(): M {
+        mutable?.let { return it }
+        return immutable.toMutable().also {
+            immutable = it
+            mutable = it
+        }
+    }
+
+    fun toImmutable(): MutableReference<I, M> = MutableReference(immutable, null)
+}
+
+interface Immutable<M> {
+    fun toMutable(): M
+}
+```
+
+Reference to any mutable data structure should be wrapped by this `MutableReference`, which
+encapsulates the logic to mutate/copy a child data structure and update the reference to the new
+child instance. It also remembers the mutated child instance so that it can be reused during further
+mutations. These `MutableReference` objects should be kept private within a data structure, with the
+`get()` method exposed on the immutable interface of the data structure as `getFoo()`, and the
+`mutate()` method exposed on the mutable interface of the data structure as `mutateFoo()`. When the
+parent data structure is mutated/copied, a new `MutableReference` object should be obtained with
+`MutableReference.toImmutable()`, which creates a new reference with the state only being immutable
+and prevents modifications to an object accessed with an immutable interface.
+
+Here is how the usage of `MutableReference` would be like in an actual class:
+
+```kotlin
+private typealias PermissionsReference =
+    MutableReference<IndexedMap<String, Permission>, MutableIndexedMap<String, Permission>>
+
+sealed class SystemState(
+    protected val permissionsReference: PermissionsReference
+) {
+    val permissions: IndexedMap<String, Permission>
+        get() = permissionsReference.get()
+}
+
+class MutableSystemState(
+    permissionsReference: PermissionsReference
+) : SystemState(permissionsRef), Immutable<MutableSystemState> {
+    fun mutatePermissions(): MutableIndexedMap<String, Permission> = permissionsReference.mutate()
+
+    override fun toMutable(): MutableSystemState =
+        MutableSystemState(permissionsReference.toImmutable())
+}
+```
+
+For collection classes like `IndexedMap`, there are also classes like `IndexedReferenceMap` where
+the values are held by `MutableReference`s, and a `mutate(key: K): V` method would help obtain a
+mutable instance of map values.
+
+## Comparison with similar solutions
+
+### Persistent data structure
+
+[Persistent data structure](https://www.wikiwand.com/en/Persistent_data_structure) is a special type
+of data structure implementation that are designed to always preserve the previous version of itself
+when it's modified. Copy-on-write data structure is a common example of it.
+
+Theoretically, persistent data structure can help eliminate the need for locking even upon
+mutations. However, in reality a lot of mutation operations may be updating multiple places in the
+tree of states, and without locking the reader might see an inconsistent state that's right in the
+middle of a mutation operation and make a wrong decision. As a result, we will still need locking
+upon mutations.
+
+Persistent data structure is also much more complex than a plain mutable data structure, both in
+terms of complexity and in terms of performance, and vastly different from the Android-specific
+collection classes that are recommended. Whereas this implementation is just a lightweight wrapper
+around the Android-specific collection classes, which allows reusing them and following the
+guidance for platform code.
+
+### `Snappable` and `Watchable` in `PackageManagerService`
+
+`Snappable` and `Watchable` is an alternative solution for lock contention and immutability.
+Basically, all the mutable state classes will need to implement a way to snapshot themselves, and a
+cache is used for each level of snapshot to reuse copies; the classes will also need to correctly
+implement change notification, so that listeners can be registered to both invalidate snapshot cache
+upon change and detect illegal mutations at run time.
+
+Here are the pros and cons of this implementation, when compared with the snapshot solution:
+
+|                        | Snapshot                                                                                                                                                                      | Immutable                                                                                                                                       |
+|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
+| Locking for reads      | Locked reads when no cached snapshot, lockless when cached                                                                                                                    | Always lockless reads                                                                                                                           |
+| Memory footprint       | Doubled memory usage for mutable data because a copy is kept in snapshot cache if ever read                                                                                   | Potentially more than necessary transient memory usage due to immutability instead of on-demand snapshot (may be mitigated for in-process code) |
+| Immutability for reads | Enforced during run time by `seal()` and `Watchable`                                                                                                                          | Enforced during compile time by type system                                                                                                     |
+| Integration complexity | A `SnapshotCache` field for every existing field, and a correctly implemented `snapshot()` method, keeps Java collection interfaces                                           | Two classes with straightforward accessors for `MutableReference` fields, less room for incorrect code, ditches Java collection interfaces      |
+| ART performance        | Non-final methods (may be made final), potential interface calls for Java collection interfaces, `Snappable` and `Watchable` interface and `instanceof` check for `Snappable` | Final methods, can't have interface call for Java/Kotlin collection interfaces, `Immutable` interface but no `instanceof` check                 |
+
+Unlike package state, permission state is far more frequently queried than mutated - mutations
+mostly happen upon first boot, or when user changes their permission decision which is rare in terms
+of the entire uptime of the system. So reads being always lockless is generally a more suitable
+design in terms of performance, and it also allows flexibility in code that have to obtain external
+state. This fact has a similar impact on the memory footprint, since most of the time the state will
+be unchanged and only read, and we should avoid having to keep another copy of it. Compile time
+enforcement of immutability for reads is safer than run time enforcement, and less room for
+incorrect integration is also an upside when both require some form of code and permission code is
+new. So all in all, the immutable data structure proposed in this document is more suitable for the
+new permission implementation.
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt
new file mode 100644
index 0000000..30b67c3
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.immutable
+
+sealed class IndexedList<T>(
+    internal val list: ArrayList<T>
+) : Immutable<MutableIndexedList<T>> {
+    val size: Int
+        get() = list.size
+
+    fun isEmpty(): Boolean = list.isEmpty()
+
+    operator fun contains(element: T): Boolean = list.contains(element)
+
+    @Suppress("ReplaceGetOrSet")
+    operator fun get(index: Int): T = list.get(index)
+
+    override fun toMutable(): MutableIndexedList<T> = MutableIndexedList(this)
+}
+
+class MutableIndexedList<T>(
+    list: ArrayList<T> = ArrayList()
+) : IndexedList<T>(list) {
+    constructor(indexedList: IndexedList<T>) : this(ArrayList(indexedList.list))
+
+    @Suppress("ReplaceGetOrSet")
+    operator fun set(index: Int, element: T): T = list.set(index, element)
+
+    fun add(element: T) {
+        list.add(element)
+    }
+
+    fun add(index: Int, element: T) {
+        list.add(index, element)
+    }
+
+    fun remove(element: T) {
+        list.remove(element)
+    }
+
+    fun clear() {
+        list.clear()
+    }
+
+    fun removeAt(index: Int): T = list.removeAt(index)
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt
new file mode 100644
index 0000000..85326c3
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.immutable
+
+inline fun <T> IndexedList<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (!predicate(index, element)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> IndexedList<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <T> IndexedList<T>.forEachIndexed(action: (Int, T) -> Unit) {
+    for (index in 0 until size) {
+        action(index, this[index])
+    }
+}
+
+inline fun <T> IndexedList<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, this[index])
+    }
+}
+
+inline val <T> IndexedList<T>.lastIndex: Int
+    get() = size - 1
+
+operator fun <T> IndexedList<T>.minus(element: T): MutableIndexedList<T> =
+    toMutable().apply { this -= element }
+
+inline fun <T> IndexedList<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
+            return false
+        }
+    }
+    return true
+}
+
+operator fun <T> IndexedList<T>.plus(element: T): MutableIndexedList<T> =
+    toMutable().apply { this += element }
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> MutableIndexedList<T>.minusAssign(element: T) {
+    remove(element)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> MutableIndexedList<T>.plusAssign(element: T) {
+    add(element)
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt
new file mode 100644
index 0000000..e744867
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.immutable
+
+sealed class IndexedListSet<T>(
+    internal val list: ArrayList<T>
+) : Immutable<MutableIndexedListSet<T>> {
+    val size: Int
+        get() = list.size
+
+    fun isEmpty(): Boolean = list.isEmpty()
+
+    operator fun contains(element: T): Boolean = list.contains(element)
+
+    fun indexOf(element: T): Int = list.indexOf(element)
+
+    @Suppress("ReplaceGetOrSet")
+    fun elementAt(index: Int): T = list.get(index)
+
+    override fun toMutable(): MutableIndexedListSet<T> = MutableIndexedListSet(this)
+}
+
+class MutableIndexedListSet<T>(
+    list: ArrayList<T> = ArrayList()
+) : IndexedListSet<T>(list) {
+    constructor(indexedListSet: IndexedListSet<T>) : this(ArrayList(indexedListSet.list))
+
+    fun add(element: T): Boolean =
+        if (list.contains(element)) {
+            false
+        } else {
+            list.add(element)
+            true
+        }
+
+    fun remove(element: T): Boolean = list.remove(element)
+
+    fun clear() {
+        list.clear()
+    }
+
+    fun removeAt(index: Int): T = list.removeAt(index)
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt
new file mode 100644
index 0000000..950d9aa
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.immutable
+
+inline fun <T> IndexedListSet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (!predicate(index, element)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> IndexedListSet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <T> IndexedListSet<T>.forEachIndexed(action: (Int, T) -> Unit) {
+    for (index in 0 until size) {
+        action(index, elementAt(index))
+    }
+}
+
+inline fun <T> IndexedListSet<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, elementAt(index))
+    }
+}
+
+inline val <T> IndexedListSet<T>.lastIndex: Int
+    get() = size - 1
+
+operator fun <T> IndexedListSet<T>.minus(element: T): MutableIndexedListSet<T> =
+    toMutable().apply { this -= element }
+
+inline fun <T> IndexedListSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
+            return false
+        }
+    }
+    return true
+}
+
+operator fun <T> IndexedListSet<T>.plus(element: T): MutableIndexedListSet<T> =
+    toMutable().apply { this += element }
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> MutableIndexedListSet<T>.minusAssign(element: T) {
+    remove(element)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> MutableIndexedListSet<T>.plusAssign(element: T) {
+    add(element)
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt
new file mode 100644
index 0000000..396a328
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.immutable
+
+import android.util.ArrayMap
+
+sealed class IndexedMap<K, V>(
+    internal val map: ArrayMap<K, V>
+) : Immutable<MutableIndexedMap<K, V>> {
+    val size: Int
+        get() = map.size
+
+    fun isEmpty(): Boolean = map.isEmpty()
+
+    operator fun contains(key: K): Boolean = map.containsKey(key)
+
+    @Suppress("ReplaceGetOrSet")
+    operator fun get(key: K): V? = map.get(key)
+
+    fun indexOfKey(key: K): Int = map.indexOfKey(key)
+
+    fun keyAt(index: Int): K = map.keyAt(index)
+
+    fun valueAt(index: Int): V = map.valueAt(index)
+
+    override fun toMutable(): MutableIndexedMap<K, V> = MutableIndexedMap(this)
+}
+
+class MutableIndexedMap<K, V>(
+    map: ArrayMap<K, V> = ArrayMap()
+) : IndexedMap<K, V>(map) {
+    constructor(indexedMap: IndexedMap<K, V>) : this(ArrayMap(indexedMap.map))
+
+    fun put(key: K, value: V): V? = map.put(key, value)
+
+    fun remove(key: K): V? = map.remove(key)
+
+    fun clear() {
+        map.clear()
+    }
+
+    fun putAt(index: Int, value: V): V = map.setValueAt(index, value)
+
+    fun removeAt(index: Int): V = map.removeAt(index)
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt
new file mode 100644
index 0000000..69f1779c
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.immutable
+
+inline fun <K, V> IndexedMap<K, V>.allIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <K, V> IndexedMap<K, V>.anyIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <K, V, R> IndexedMap<K, V>.firstNotNullOfOrNullIndexed(transform: (Int, K, V) -> R): R? {
+    forEachIndexed { index, key, value ->
+        transform(index, key, value)?.let { return it }
+    }
+    return null
+}
+
+inline fun <K, V> IndexedMap<K, V>.forEachIndexed(action: (Int, K, V) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <K, V> IndexedMap<K, V>.forEachReversedIndexed(action: (Int, K, V) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+fun <K, V> IndexedMap<K, V>?.getWithDefault(key: K, defaultValue: V): V {
+    this ?: return defaultValue
+    val index = indexOfKey(key)
+    return if (index >= 0) valueAt(index) else defaultValue
+}
+
+inline val <K, V> IndexedMap<K, V>.lastIndex: Int
+    get() = size - 1
+
+inline fun <K, V> IndexedMap<K, V>.noneIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <K, V, R, C : MutableCollection<R>> IndexedMap<K, V>.mapIndexedTo(
+    destination: C,
+    transform: (Int, K, V) -> R,
+): C {
+    forEachIndexed { index, key, value ->
+        transform(index, key, value).let { destination += it }
+    }
+    return destination
+}
+
+inline fun <K, V, R, C : MutableCollection<R>> IndexedMap<K, V>.mapNotNullIndexedTo(
+    destination: C,
+    transform: (Int, K, V) -> R?
+): C {
+    forEachIndexed { index, key, value ->
+        transform(index, key, value)?.let { destination += it }
+    }
+    return destination
+}
+
+inline fun <K, V> MutableIndexedMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V {
+    get(key)?.let { return it }
+    return defaultValue().also { put(key, it) }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <K, V> MutableIndexedMap<K, V>.minusAssign(key: K) {
+    remove(key)
+}
+
+fun <K, V> MutableIndexedMap<K, V>.putWithDefault(key: K, value: V, defaultValue: V): V {
+    val index = indexOfKey(key)
+    if (index >= 0) {
+        val oldValue = valueAt(index)
+        if (value != oldValue) {
+            if (value == defaultValue) {
+                removeAt(index)
+            } else {
+                putAt(index, value)
+            }
+        }
+        return oldValue
+    } else {
+        if (value != defaultValue) {
+            put(key, value)
+        }
+        return defaultValue
+    }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <K, V> MutableIndexedMap<K, V>.set(key: K, value: V) {
+    put(key, value)
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
new file mode 100644
index 0000000..3869b57
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.immutable
+
+import android.util.ArrayMap
+
+sealed class IndexedReferenceMap<K, I : Immutable<M>, M : I>(
+    internal val map: ArrayMap<K, MutableReference<I, M>>
+) : Immutable<MutableIndexedReferenceMap<K, I, M>> {
+    val size: Int
+        get() = map.size
+
+    fun isEmpty(): Boolean = map.isEmpty()
+
+    operator fun contains(key: K): Boolean = map.containsKey(key)
+
+    @Suppress("ReplaceGetOrSet")
+    operator fun get(key: K): I? = map.get(key)?.get()
+
+    fun indexOfKey(key: K): Int = map.indexOfKey(key)
+
+    fun keyAt(index: Int): K = map.keyAt(index)
+
+    fun valueAt(index: Int): I = map.valueAt(index).get()
+
+    override fun toMutable(): MutableIndexedReferenceMap<K, I, M> = MutableIndexedReferenceMap(this)
+}
+
+class MutableIndexedReferenceMap<K, I : Immutable<M>, M : I>(
+    map: ArrayMap<K, MutableReference<I, M>> = ArrayMap()
+) : IndexedReferenceMap<K, I, M>(map) {
+    constructor(indexedReferenceMap: IndexedReferenceMap<K, I, M>) : this(
+        ArrayMap(indexedReferenceMap.map).apply {
+            for (i in 0 until size) {
+                setValueAt(i, valueAt(i).toImmutable())
+            }
+        }
+    )
+
+    @Suppress("ReplaceGetOrSet")
+    fun mutate(key: K): M? = map.get(key)?.mutate()
+
+    fun put(key: K, value: M): I? = map.put(key, MutableReference(value))?.get()
+
+    fun remove(key: K): I? = map.remove(key)?.get()
+
+    fun clear() {
+        map.clear()
+    }
+
+    fun mutateAt(index: Int): M = map.valueAt(index).mutate()
+
+    fun putAt(index: Int, value: M): I = map.setValueAt(index, MutableReference(value)).get()
+
+    fun removeAt(index: Int): I = map.removeAt(index).get()
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt
new file mode 100644
index 0000000..22b4d52
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.immutable
+
+inline fun <K, I : Immutable<M>, M : I> IndexedReferenceMap<K, I, M>.allIndexed(
+    predicate: (Int, K, I) -> Boolean
+): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <K, I : Immutable<M>, M : I> IndexedReferenceMap<K, I, M>.anyIndexed(
+    predicate: (Int, K, I) -> Boolean
+): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <K, I : Immutable<M>, M : I> IndexedReferenceMap<K, I, M>.forEachIndexed(
+    action: (Int, K, I) -> Unit
+) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <K, I : Immutable<M>, M : I> IndexedReferenceMap<K, I, M>.forEachReversedIndexed(
+    action: (Int, K, I) -> Unit
+) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline val <K, I : Immutable<M>, M : I> IndexedReferenceMap<K, I, M>.lastIndex: Int
+    get() = size - 1
+
+inline fun <K, I : Immutable<M>, M : I> IndexedReferenceMap<K, I, M>.noneIndexed(
+    predicate: (Int, K, I) -> Boolean
+): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <K, I : Immutable<M>, M : I> MutableIndexedReferenceMap<K, I, M>.mutateOrPut(
+    key: K,
+    defaultValue: () -> M
+): M {
+    mutate(key)?.let { return it }
+    return defaultValue().also { put(key, it) }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <K, I : Immutable<M>, M : I> MutableIndexedReferenceMap<K, I, M>.minusAssign(
+    key: K
+) {
+    remove(key)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <K, I : Immutable<M>, M : I> MutableIndexedReferenceMap<K, I, M>.set(
+    key: K,
+    value: M
+) {
+    put(key, value)
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
new file mode 100644
index 0000000..c7c0498
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.immutable
+
+import android.util.ArraySet
+
+sealed class IndexedSet<T>(
+    internal val set: ArraySet<T>
+) : Immutable<MutableIndexedSet<T>> {
+    val size: Int
+        get() = set.size
+
+    fun isEmpty(): Boolean = set.isEmpty()
+
+    operator fun contains(element: T): Boolean = set.contains(element)
+
+    fun indexOf(element: T): Int = set.indexOf(element)
+
+    fun elementAt(index: Int): T = set.elementAt(index)
+
+    override fun toMutable(): MutableIndexedSet<T> = MutableIndexedSet(this)
+}
+
+class MutableIndexedSet<T>(
+    set: ArraySet<T> = ArraySet()
+) : IndexedSet<T>(set) {
+    constructor(indexedSet: IndexedSet<T>) : this(ArraySet(indexedSet.set))
+
+    fun add(element: T): Boolean = set.add(element)
+
+    fun remove(element: T): Boolean = set.remove(element)
+
+    fun clear() {
+        set.clear()
+    }
+
+    fun removeAt(index: Int): T = set.removeAt(index)
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedSetExtensions.kt
new file mode 100644
index 0000000..2cc1b2a
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedSetExtensions.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.immutable
+
+import android.util.ArraySet
+import com.android.server.permission.access.collection.forEachIndexed
+
+fun <T> indexedSetOf(vararg elements: T): IndexedSet<T> =
+    MutableIndexedSet(ArraySet(elements.asList()))
+
+inline fun <T> IndexedSet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (!predicate(index, element)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> IndexedSet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <T> IndexedSet<T>.forEachIndexed(action: (Int, T) -> Unit) {
+    for (index in 0 until size) {
+        action(index, elementAt(index))
+    }
+}
+
+inline fun <T> IndexedSet<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, elementAt(index))
+    }
+}
+
+inline val <T> IndexedSet<T>.lastIndex: Int
+    get() = size - 1
+
+operator fun <T> IndexedSet<T>.minus(element: T): MutableIndexedSet<T> =
+    toMutable().apply { this -= element }
+
+inline fun <T> IndexedSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
+            return false
+        }
+    }
+    return true
+}
+
+operator fun <T> IndexedSet<T>.plus(element: T): MutableIndexedSet<T> =
+    toMutable().apply { this += element }
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> MutableIndexedSet<T>.minusAssign(element: T) {
+    remove(element)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> MutableIndexedSet<T>.plusAssign(element: T) {
+    add(element)
+}
+
+operator fun <T> MutableIndexedSet<T>.plusAssign(list: List<T>) {
+    list.forEachIndexed { _, it -> this += it }
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt
new file mode 100644
index 0000000..a846050
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.immutable
+
+import android.util.SparseArray
+
+sealed class IntMap<T>(
+    internal val array: SparseArray<T>
+) : Immutable<MutableIntMap<T>> {
+    val size: Int
+        get() = array.size()
+
+    fun isEmpty(): Boolean = array.size() == 0
+
+    operator fun contains(key: Int): Boolean = array.contains(key)
+
+    operator fun get(key: Int): T? = array.get(key)
+
+    fun indexOfKey(key: Int): Int = array.indexOfKey(key)
+
+    fun keyAt(index: Int): Int = array.keyAt(index)
+
+    fun valueAt(index: Int): T = array.valueAt(index)
+
+    override fun toMutable(): MutableIntMap<T> = MutableIntMap(this)
+}
+
+class MutableIntMap<T>(
+    array: SparseArray<T> = SparseArray()
+) : IntMap<T>(array) {
+    constructor(intMap: IntMap<T>) : this(intMap.array.clone())
+
+    fun put(key: Int, value: T): T? = array.putReturnOld(key, value)
+
+    fun remove(key: Int): T? = array.removeReturnOld(key)
+
+    fun clear() {
+        array.clear()
+    }
+
+    fun putAt(index: Int, value: T): T = array.setValueAtReturnOld(index, value)
+
+    fun removeAt(index: Int): T = array.removeAtReturnOld(index)
+}
+
+internal fun <T> SparseArray<T>.putReturnOld(key: Int, value: T): T? {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        val oldValue = valueAt(index)
+        setValueAt(index, value)
+        oldValue
+    } else {
+        put(key, value)
+        null
+    }
+}
+
+// SparseArray.removeReturnOld() is @hide, so a backup once we move to APIs.
+@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
+internal fun <T> SparseArray<T>.removeReturnOld(key: Int): T? {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        val oldValue = valueAt(index)
+        removeAt(index)
+        oldValue
+    } else {
+        null
+    }
+}
+
+internal fun <T> SparseArray<T>.setValueAtReturnOld(index: Int, value: T): T {
+    val oldValue = valueAt(index)
+    setValueAt(index, value)
+    return oldValue
+}
+
+internal fun <T> SparseArray<T>.removeAtReturnOld(index: Int): T {
+    val oldValue = valueAt(index)
+    removeAt(index)
+    return oldValue
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt
new file mode 100644
index 0000000..ed7f0af
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.immutable
+
+inline fun <T> IntMap<T>.allIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> IntMap<T>.anyIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <T, R> IntMap<T>.firstNotNullOfOrNullIndexed(transform: (Int, Int, T) -> R): R? {
+    forEachIndexed { index, key, value ->
+        transform(index, key, value)?.let { return it }
+    }
+    return null
+}
+
+inline fun <T> IntMap<T>.forEachIndexed(action: (Int, Int, T) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <T> IntMap<T>.forEachReversedIndexed(action: (Int, Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+fun <T> IntMap<T>?.getWithDefault(key: Int, defaultValue: T): T {
+    this ?: return defaultValue
+    val index = indexOfKey(key)
+    return if (index >= 0) valueAt(index) else defaultValue
+}
+
+inline val <T> IntMap<T>.lastIndex: Int
+    get() = size - 1
+
+inline fun <T> IntMap<T>.noneIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> MutableIntMap<T>.getOrPut(key: Int, defaultValue: () -> T): T {
+    get(key)?.let { return it }
+    return defaultValue().also { put(key, it) }
+}
+
+operator fun <T> MutableIntMap<T>.minusAssign(key: Int) {
+    array.remove(key)
+}
+
+fun <T> MutableIntMap<T>.putWithDefault(key: Int, value: T, defaultValue: T): T {
+    val index = indexOfKey(key)
+    if (index >= 0) {
+        val oldValue = valueAt(index)
+        if (value != oldValue) {
+            if (value == defaultValue) {
+                removeAt(index)
+            } else {
+                putAt(index, value)
+            }
+        }
+        return oldValue
+    } else {
+        if (value != defaultValue) {
+            put(key, value)
+        }
+        return defaultValue
+    }
+}
+
+operator fun <T> MutableIntMap<T>.set(key: Int, value: T) {
+    array.put(key, value)
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt
new file mode 100644
index 0000000..519a36f
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.immutable
+
+import android.util.SparseArray
+
+sealed class IntReferenceMap<I : Immutable<M>, M : I>(
+    internal val array: SparseArray<MutableReference<I, M>>
+) : Immutable<MutableIntReferenceMap<I, M>> {
+    val size: Int
+        get() = array.size()
+
+    fun isEmpty(): Boolean = array.size() == 0
+
+    operator fun contains(key: Int): Boolean = array.contains(key)
+
+    @Suppress("ReplaceGetOrSet")
+    operator fun get(key: Int): I? = array.get(key)?.get()
+
+    fun indexOfKey(key: Int): Int = array.indexOfKey(key)
+
+    fun keyAt(index: Int): Int = array.keyAt(index)
+
+    fun valueAt(index: Int): I = array.valueAt(index).get()
+
+    override fun toMutable(): MutableIntReferenceMap<I, M> = MutableIntReferenceMap(this)
+}
+
+class MutableIntReferenceMap<I : Immutable<M>, M : I>(
+    array: SparseArray<MutableReference<I, M>> = SparseArray()
+) : IntReferenceMap<I, M>(array) {
+    constructor(intReferenceMap: IntReferenceMap<I, M>) : this(
+        intReferenceMap.array.clone().apply {
+            for (i in 0 until size()) {
+                setValueAt(i, valueAt(i).toImmutable())
+            }
+        }
+    )
+
+    @Suppress("ReplaceGetOrSet")
+    fun mutate(key: Int): M? = array.get(key)?.mutate()
+
+    fun put(key: Int, value: M): I? = array.putReturnOld(key, MutableReference(value))?.get()
+
+    fun remove(key: Int): I? = array.removeReturnOld(key)?.get()
+
+    fun clear() {
+        array.clear()
+    }
+
+    fun mutateAt(index: Int): M = array.valueAt(index).mutate()
+
+    fun putAt(index: Int, value: M): I =
+        array.setValueAtReturnOld(index, MutableReference(value)).get()
+
+    fun removeAt(index: Int): I = array.removeAtReturnOld(index).get()
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt
new file mode 100644
index 0000000..b4de5d1
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.immutable
+
+inline fun <I : Immutable<M>, M : I> IntReferenceMap<I, M>.allIndexed(
+    predicate: (Int, Int, I) -> Boolean
+): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <I : Immutable<M>, M : I> IntReferenceMap<I, M>.anyIndexed(
+    predicate: (Int, Int, I) -> Boolean
+): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <I : Immutable<M>, M : I> IntReferenceMap<I, M>.forEachIndexed(
+    action: (Int, Int, I) -> Unit
+) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <I : Immutable<M>, M : I> IntReferenceMap<I, M>.forEachReversedIndexed(
+    action: (Int, Int, I) -> Unit
+) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline val <I : Immutable<M>, M : I> IntReferenceMap<I, M>.lastIndex: Int
+    get() = size - 1
+
+inline fun <I : Immutable<M>, M : I> IntReferenceMap<I, M>.noneIndexed(
+    predicate: (Int, Int, I) -> Boolean
+): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <I : Immutable<M>, M : I> MutableIntReferenceMap<I, M>.mutateOrPut(
+    key: Int,
+    defaultValue: () -> M
+): M {
+    mutate(key)?.let { return it }
+    return defaultValue().also { put(key, it) }
+}
+
+operator fun <I : Immutable<M>, M : I> MutableIntReferenceMap<I, M>.minusAssign(key: Int) {
+    array.remove(key)
+}
+
+operator fun <I : Immutable<M>, M : I> MutableIntReferenceMap<I, M>.set(key: Int, value: M) {
+    array.put(key, MutableReference(value))
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt
new file mode 100644
index 0000000..1fd247b
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.immutable
+
+import android.util.SparseBooleanArray
+
+sealed class IntSet(
+    internal val array: SparseBooleanArray
+) : Immutable<MutableIntSet> {
+    val size: Int
+        get() = array.size()
+
+    fun isEmpty(): Boolean = array.size() == 0
+
+    operator fun contains(element: Int): Boolean = array.contains(element)
+
+    fun indexOf(element: Int): Int = array.indexOfKey(element)
+
+    fun elementAt(index: Int): Int = array.keyAt(index)
+
+    override fun toMutable(): MutableIntSet = MutableIntSet(this)
+}
+
+class MutableIntSet(
+    array: SparseBooleanArray = SparseBooleanArray()
+) : IntSet(array) {
+    constructor(intSet: IntSet) : this(intSet.array.clone())
+
+    fun add(element: Int): Boolean =
+        if (array.contains(element)) {
+            false
+        } else {
+            array.put(element, true)
+            true
+        }
+
+    fun remove(element: Int): Boolean {
+        val index = array.indexOfKey(element)
+        return if (index >= 0) {
+            array.removeAt(index)
+            true
+        } else {
+            false
+        }
+    }
+
+    fun clear() {
+        array.clear()
+    }
+
+    fun removeAt(index: Int) {
+        array.removeAt(index)
+    }
+}
+
+// Unlike SparseArray, SparseBooleanArray is missing this method.
+private fun SparseBooleanArray.contains(key: Int): Boolean = indexOfKey(key) >= 0
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt
new file mode 100644
index 0000000..163ebbf
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.immutable
+
+inline fun IntSet.allIndexed(predicate: (Int, Int) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (!predicate(index, element)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun IntSet.anyIndexed(predicate: (Int, Int) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun IntSet.forEachIndexed(action: (Int, Int) -> Unit) {
+    for (index in 0 until size) {
+        action(index, elementAt(index))
+    }
+}
+
+inline fun IntSet.forEachReversedIndexed(action: (Int, Int) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, elementAt(index))
+    }
+}
+
+inline val IntSet.lastIndex: Int
+    get() = size - 1
+
+operator fun IntSet.minus(element: Int): MutableIntSet = toMutable().apply { this -= element }
+
+operator fun IntSet.minusAssign(element: Int) {
+    array.delete(element)
+}
+
+inline fun IntSet.noneIndexed(predicate: (Int, Int) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
+            return false
+        }
+    }
+    return true
+}
+
+operator fun IntSet.plus(element: Int): MutableIntSet = toMutable().apply { this += element }
+
+fun MutableIntSet(values: IntArray): MutableIntSet = MutableIntSet().apply{ this += values }
+
+operator fun MutableIntSet.plusAssign(element: Int) {
+    array.put(element, true)
+}
+
+operator fun MutableIntSet.plusAssign(set: IntSet) {
+    set.forEachIndexed { _, it -> this += it }
+}
+
+operator fun MutableIntSet.plusAssign(array: IntArray) {
+    array.forEach { this += it }
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
new file mode 100644
index 0000000..e39a3bb
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.immutable
+
+class MutableReference<I : Immutable<M>, M : I> private constructor(
+    private var immutable: I,
+    private var mutable: M?
+) {
+    constructor(mutable: M) : this(mutable, mutable)
+
+    fun get(): I = immutable
+
+    fun mutate(): M {
+        mutable?.let { return it }
+        return immutable.toMutable().also {
+            immutable = it
+            mutable = it
+        }
+    }
+
+    fun toImmutable(): MutableReference<I, M> = MutableReference(immutable, null)
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) {
+            return true
+        }
+        if (javaClass != other?.javaClass) {
+            return false
+        }
+        other as MutableReference<*, *>
+        return immutable == other.immutable
+    }
+
+    override fun hashCode(): Int = immutable.hashCode()
+}
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt
index b2a27b2..26053f0 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt
@@ -18,8 +18,8 @@
 
 import android.util.Log
 import com.android.server.LocalServices
-import com.android.server.permission.access.AccessState
-import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.util.PackageVersionMigration
 import com.android.server.pm.permission.PermissionMigrationHelper
 
@@ -27,17 +27,17 @@
  * This class migrate legacy permissions to unified permission subsystem
  */
 class AppIdPermissionMigration {
-    internal fun migrateSystemState(state: AccessState) {
+    internal fun migrateSystemState(state: MutableAccessState) {
         val legacyPermissionsManager =
             LocalServices.getService(PermissionMigrationHelper::class.java)!!
-        migratePermissions(state.systemState.permissions,
+        migratePermissions(state.mutateSystemState().mutatePermissions(),
             legacyPermissionsManager.legacyPermissions)
-        migratePermissions(state.systemState.permissionTrees,
+        migratePermissions(state.mutateSystemState().mutatePermissions(),
             legacyPermissionsManager.legacyPermissionTrees, true)
     }
 
     private fun migratePermissions(
-        permissions: IndexedMap<String, Permission>,
+        permissions: MutableIndexedMap<String, Permission>,
         legacyPermissions: Map<String, PermissionMigrationHelper.LegacyPermission>,
         isPermissionTree: Boolean = false
     ) {
@@ -55,72 +55,89 @@
         }
     }
 
-    internal fun migrateUserState(state: AccessState, userId: Int) {
+    internal fun migrateUserState(state: MutableAccessState, userId: Int) {
         val permissionMigrationHelper =
             LocalServices.getService(PermissionMigrationHelper::class.java)!!
-        val permissionStates = permissionMigrationHelper.getLegacyPermissionStates(userId)
+        val legacyAppIdPermissionStates =
+            permissionMigrationHelper.getLegacyPermissionStates(userId)
         val version = PackageVersionMigration.getVersion(userId)
 
-        permissionStates.forEach { (appId, permissionStates) ->
-            migratePermissionStates(appId, state, permissionStates, userId)
-            state.systemState.appIds[appId].forEachIndexed { _, packageName ->
-                state.userStates[userId].packageVersions[packageName] = version
+        val userState = state.mutateUserState(userId)!!
+        val appIdPermissionFlags = userState.mutateAppIdPermissionFlags()
+        legacyAppIdPermissionStates.forEach { (appId, legacyPermissionStates) ->
+            val packageNames = state.systemState.appIdPackageNames[appId]
+            if (packageNames == null) {
+                Log.w(LOG_TAG, "Dropping unknown app ID $appId when migrating permission state")
+                return@forEach
+            }
+
+            val permissionFlags = MutableIndexedMap<String, Int>()
+            appIdPermissionFlags[appId] = permissionFlags
+            legacyPermissionStates.forEach forEachPermission@ {
+                (permissionName, legacyPermissionState) ->
+                val permission = state.systemState.permissions[permissionName]
+                if (permission == null) {
+                    Log.w(
+                        LOG_TAG, "Dropping unknown permission $permissionName for app ID $appId" +
+                            " when migrating permission state"
+                    )
+                    return@forEachPermission
+                }
+                permissionFlags[permissionName] = migratePermissionFlags(
+                    permission, legacyPermissionState, appId, userId
+                )
+            }
+
+            val packageVersions = userState.mutatePackageVersions()
+            packageNames.forEachIndexed { _, packageName ->
+                packageVersions[packageName] = version
             }
         }
     }
 
-    private fun migratePermissionStates(
+    private fun migratePermissionFlags(
+        permission: Permission,
+        legacyPermissionState: PermissionMigrationHelper.LegacyPermissionState,
         appId: Int,
-        state: AccessState,
-        legacyPermissionStates: Map<String, PermissionMigrationHelper.LegacyPermissionState>,
         userId: Int
-    ) {
-        val permissionFlags =
-            state.userStates[userId].appIdPermissionFlags.getOrPut(appId) { IndexedMap() }
-
-        legacyPermissionStates.forEach forEachPermission@ { (permissionName, permissionState) ->
-            val permission = state.systemState.permissions[permissionName]
-                ?: return@forEachPermission
-
-            var flags = when {
-                permission.isNormal -> if (permissionState.isGranted) {
-                    PermissionFlags.INSTALL_GRANTED
-                } else {
-                    PermissionFlags.INSTALL_REVOKED
-                }
-                permission.isSignature || permission.isInternal ->
-                    if (permissionState.isGranted) {
-                        if (permission.isDevelopment || permission.isRole) {
-                            PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED
-                        } else {
-                            PermissionFlags.PROTECTION_GRANTED
-                        }
+    ): Int {
+        var flags = when {
+            permission.isNormal -> if (legacyPermissionState.isGranted) {
+                PermissionFlags.INSTALL_GRANTED
+            } else {
+                PermissionFlags.INSTALL_REVOKED
+            }
+            permission.isSignature || permission.isInternal ->
+                if (legacyPermissionState.isGranted) {
+                    if (permission.isDevelopment || permission.isRole) {
+                        PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED
                     } else {
-                        0
+                        PermissionFlags.PROTECTION_GRANTED
                     }
-                permission.isRuntime ->
-                    if (permissionState.isGranted) PermissionFlags.RUNTIME_GRANTED else 0
-                else -> 0
-            }
-            flags = PermissionFlags.updateFlags(
-                permission, flags, permissionState.flags, permissionState.flags
-            )
-            permissionFlags[permissionName] = flags
-
-            if (DEBUG_MIGRATION) {
-                val oldFlagString = PermissionFlags.apiFlagsToString(permissionState.flags)
-                val newFlagString = PermissionFlags.toString(flags)
-                val oldGrantState = permissionState.isGranted
-                val newGrantState = PermissionFlags.isPermissionGranted(flags)
-                val flagsMismatch = permissionState.flags != PermissionFlags.toApiFlags(flags)
-                Log.v(
-                    LOG_TAG, "Migrated appId: $appId, permission: " +
-                        "${permission.name}, user: $userId, oldGrantState: $oldGrantState" +
-                        ", oldFlags: $oldFlagString, newFlags: $newFlagString, grantMismatch: " +
-                        "${oldGrantState != newGrantState}, flagsMismatch: $flagsMismatch"
-                )
-            }
+                } else {
+                    0
+                }
+            permission.isRuntime ->
+                if (legacyPermissionState.isGranted) PermissionFlags.RUNTIME_GRANTED else 0
+            else -> 0
         }
+        flags = PermissionFlags.updateFlags(
+            permission, flags, legacyPermissionState.flags, legacyPermissionState.flags
+        )
+        if (DEBUG_MIGRATION) {
+            val oldFlagString = PermissionFlags.apiFlagsToString(legacyPermissionState.flags)
+            val newFlagString = PermissionFlags.toString(flags)
+            val oldGrantState = legacyPermissionState.isGranted
+            val newGrantState = PermissionFlags.isPermissionGranted(flags)
+            val flagsMismatch = legacyPermissionState.flags != PermissionFlags.toApiFlags(flags)
+            Log.v(
+                LOG_TAG, "Migrated appId: $appId, permission: " +
+                    "${permission.name}, user: $userId, oldGrantState: $oldGrantState" +
+                    ", oldFlags: $oldFlagString, newFlags: $newFlagString, grantMismatch: " +
+                    "${oldGrantState != newGrantState}, flagsMismatch: $flagsMismatch"
+            )
+        }
+        return flags
     }
 
     companion object {
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt
index 552dda1..0f94b0f 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt
@@ -21,8 +21,12 @@
 import com.android.modules.utils.BinaryXmlPullParser
 import com.android.modules.utils.BinaryXmlSerializer
 import com.android.server.permission.access.AccessState
-import com.android.server.permission.access.UserState
+import com.android.server.permission.access.AppIdPermissionFlags
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.MutableAppIdPermissionFlags
+import com.android.server.permission.access.WriteMode
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.util.attribute
 import com.android.server.permission.access.util.attributeInt
 import com.android.server.permission.access.util.attributeIntHex
@@ -38,16 +42,18 @@
 import com.android.server.permission.access.util.tagName
 
 class AppIdPermissionPersistence {
-    fun BinaryXmlPullParser.parseSystemState(state: AccessState) {
-        val systemState = state.systemState
+    fun BinaryXmlPullParser.parseSystemState(state: MutableAccessState) {
+        val systemState = state.mutateSystemState(WriteMode.NONE)
         when (tagName) {
-            TAG_PERMISSION_TREES -> parsePermissions(systemState.permissionTrees)
-            TAG_PERMISSIONS -> parsePermissions(systemState.permissions)
+            TAG_PERMISSION_TREES -> parsePermissions(systemState.mutatePermissionTrees())
+            TAG_PERMISSIONS -> parsePermissions(systemState.mutatePermissions())
             else -> {}
         }
     }
 
-    private fun BinaryXmlPullParser.parsePermissions(permissions: IndexedMap<String, Permission>) {
+    private fun BinaryXmlPullParser.parsePermissions(
+        permissions: MutableIndexedMap<String, Permission>
+    ) {
         forEachTag {
             when (val tagName = tagName) {
                 TAG_PERMISSION -> parsePermission(permissions)
@@ -56,7 +62,9 @@
         }
     }
 
-    private fun BinaryXmlPullParser.parsePermission(permissions: IndexedMap<String, Permission>) {
+    private fun BinaryXmlPullParser.parsePermission(
+        permissions: MutableIndexedMap<String, Permission>
+    ) {
         val name = getAttributeValueOrThrow(ATTR_NAME).intern()
         @Suppress("DEPRECATION")
         val permissionInfo = PermissionInfo().apply {
@@ -97,7 +105,7 @@
         permissions: IndexedMap<String, Permission>
     ) {
         tag(tagName) {
-            permissions.forEachValueIndexed { _, it -> serializePermission(it) }
+            permissions.forEachIndexed { _, _, it -> serializePermission(it) }
         }
     }
 
@@ -124,61 +132,60 @@
         }
     }
 
-    fun BinaryXmlPullParser.parseUserState(state: AccessState, userId: Int) {
+    fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
         when (tagName) {
             TAG_APP_ID_PERMISSIONS -> parseAppIdPermissions(state, userId)
             else -> {}
         }
     }
 
-    private fun BinaryXmlPullParser.parseAppIdPermissions(state: AccessState, userId: Int) {
-        val userState = state.userStates[userId]
+    private fun BinaryXmlPullParser.parseAppIdPermissions(state: MutableAccessState, userId: Int) {
+        val userState = state.mutateUserState(userId, WriteMode.NONE)!!
+        val appIdPermissionFlags = userState.mutateAppIdPermissionFlags()
         forEachTag {
             when (tagName) {
-                TAG_APP_ID -> parseAppId(userState)
+                TAG_APP_ID -> parseAppId(appIdPermissionFlags)
                 else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
             }
         }
-        userState.appIdPermissionFlags.retainAllIndexed { _, appId, _ ->
-            val hasAppId = appId in state.systemState.appIds
-            if (!hasAppId) {
+        appIdPermissionFlags.forEachReversedIndexed { appIdIndex, appId, _ ->
+            if (appId !in state.systemState.appIdPackageNames) {
                 Log.w(LOG_TAG, "Dropping unknown app ID $appId when parsing permission state")
+                appIdPermissionFlags.removeAt(appIdIndex)
+                userState.requestWriteMode(WriteMode.ASYNCHRONOUS)
             }
-            hasAppId
         }
     }
 
-    private fun BinaryXmlPullParser.parseAppId(userState: UserState) {
+    private fun BinaryXmlPullParser.parseAppId(appIdPermissionFlags: MutableAppIdPermissionFlags) {
         val appId = getAttributeIntOrThrow(ATTR_ID)
-        val permissionFlags = IndexedMap<String, Int>()
-        userState.appIdPermissionFlags[appId] = permissionFlags
-        parsePermissionStates(permissionFlags)
-    }
-
-    private fun BinaryXmlPullParser.parsePermissionStates(
-        permissionFlags: IndexedMap<String, Int>
-    ) {
+        val permissionFlags = MutableIndexedMap<String, Int>()
+        appIdPermissionFlags[appId] = permissionFlags
         forEachTag {
             when (tagName) {
-                TAG_PERMISSION -> parsePermissionState(permissionFlags)
+                TAG_PERMISSION -> parseAppIdPermission(permissionFlags)
                 else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
             }
         }
     }
 
-    private fun BinaryXmlPullParser.parsePermissionState(permissionFlags: IndexedMap<String, Int>) {
+    private fun BinaryXmlPullParser.parseAppIdPermission(
+        permissionFlags: MutableIndexedMap<String, Int>
+    ) {
         val name = getAttributeValueOrThrow(ATTR_NAME).intern()
         val flags = getAttributeIntOrThrow(ATTR_FLAGS)
         permissionFlags[name] = flags
     }
 
     fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
-        serializeAppIdPermissions(state.userStates[userId])
+        serializeAppIdPermissions(state.userStates[userId]!!.appIdPermissionFlags)
     }
 
-    private fun BinaryXmlSerializer.serializeAppIdPermissions(userState: UserState) {
+    private fun BinaryXmlSerializer.serializeAppIdPermissions(
+        appIdPermissionFlags: AppIdPermissionFlags
+    ) {
         tag(TAG_APP_ID_PERMISSIONS) {
-            userState.appIdPermissionFlags.forEachIndexed { _, appId, permissionFlags ->
+            appIdPermissionFlags.forEachIndexed { _, appId, permissionFlags ->
                 serializeAppId(appId, permissionFlags)
             }
         }
@@ -190,19 +197,13 @@
     ) {
         tag(TAG_APP_ID) {
             attributeInt(ATTR_ID, appId)
-            serializePermissionStates(permissionFlags)
+            permissionFlags.forEachIndexed { _, name, flags ->
+                serializeAppIdPermission(name, flags)
+            }
         }
     }
 
-    private fun BinaryXmlSerializer.serializePermissionStates(
-        permissionFlags: IndexedMap<String, Int>
-    ) {
-        permissionFlags.forEachIndexed { _, name, flags ->
-            serializePermissionState(name, flags)
-        }
-    }
-
-    private fun BinaryXmlSerializer.serializePermissionState(name: String, flags: Int) {
+    private fun BinaryXmlSerializer.serializeAppIdPermission(name: String, flags: Int) {
         tag(TAG_PERMISSION) {
             attributeInterned(ATTR_NAME, name)
             attributeInt(ATTR_FLAGS, flags)
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 1177735..992edc2 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -30,11 +30,14 @@
 import com.android.server.permission.access.AccessState
 import com.android.server.permission.access.AccessUri
 import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutableAccessState
 import com.android.server.permission.access.MutateStateScope
 import com.android.server.permission.access.PermissionUri
 import com.android.server.permission.access.SchemePolicy
 import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.WriteMode
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.util.andInv
 import com.android.server.permission.access.util.hasAnyBit
 import com.android.server.permission.access.util.hasBits
@@ -53,11 +56,11 @@
     private val upgrade = AppIdPermissionUpgrade(this)
 
     @Volatile
-    private var onPermissionFlagsChangedListeners =
-        IndexedListSet<OnPermissionFlagsChangedListener>()
+    private var onPermissionFlagsChangedListeners:
+        IndexedListSet<OnPermissionFlagsChangedListener> = MutableIndexedListSet()
     private val onPermissionFlagsChangedListenersLock = Any()
 
-    private val privilegedPermissionAllowlistViolations = IndexedSet<String>()
+    private val privilegedPermissionAllowlistViolations = MutableIndexedSet<String>()
 
     override val subjectScheme: String
         get() = UidUri.SCHEME
@@ -87,8 +90,7 @@
 
     override fun MutateStateScope.onInitialized() {
         newState.systemState.configPermissions.forEach { (permissionName, permissionEntry) ->
-            val permissions = newState.systemState.permissions
-            val oldPermission = permissions[permissionName]
+            val oldPermission = newState.systemState.permissions[permissionName]
             val newPermission = if (oldPermission != null) {
                 if (permissionEntry.gids != null) {
                     oldPermission.copy(
@@ -113,7 +115,7 @@
                     Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0)
                 }
             }
-            permissions[permissionName] = newPermission
+            newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission
         }
     }
 
@@ -121,16 +123,17 @@
         newState.systemState.packageStates.forEach { (_, packageState) ->
             evaluateAllPermissionStatesForPackageAndUser(packageState, userId, null)
         }
-        newState.systemState.appIds.forEachKeyIndexed { _, appId ->
+        newState.systemState.appIdPackageNames.forEachIndexed { _, appId, _ ->
             inheritImplicitPermissionStates(appId, userId)
         }
     }
 
     override fun MutateStateScope.onAppIdRemoved(appId: Int) {
-        newState.userStates.forEachValueIndexed { _, userState ->
-            userState.appIdPermissionFlags -= appId
-            userState.requestWrite()
-            // Skip notifying the change listeners since the app ID no longer exists.
+        newState.userStates.forEachIndexed { userStateIndex, _, userState ->
+            if (appId in userState.appIdPermissionFlags) {
+                newState.mutateUserStateAt(userStateIndex).mutateAppIdPermissionFlags() -= appId
+                // Skip notifying the change listeners since the app ID no longer exists.
+            }
         }
     }
 
@@ -138,7 +141,7 @@
         volumeUuid: String?,
         isSystemUpdated: Boolean
     ) {
-        val changedPermissionNames = IndexedSet<String>()
+        val changedPermissionNames = MutableIndexedSet<String>()
         newState.systemState.packageStates.forEach { (_, packageState) ->
             val androidPackage = packageState.androidPackage
             if (androidPackage == null || androidPackage.volumeUuid != volumeUuid) {
@@ -175,7 +178,7 @@
     }
 
     override fun MutateStateScope.onPackageAdded(packageState: PackageState) {
-        val changedPermissionNames = IndexedSet<String>()
+        val changedPermissionNames = MutableIndexedSet<String>()
         adoptPermissions(packageState, changedPermissionNames)
         addPermissionGroups(packageState)
         addPermissions(packageState, changedPermissionNames)
@@ -198,9 +201,9 @@
             "Package $packageName reported as removed before disabled system package is enabled"
         }
 
-        val changedPermissionNames = IndexedSet<String>()
+        val changedPermissionNames = MutableIndexedSet<String>()
         trimPermissions(packageName, changedPermissionNames)
-        if (appId in newState.systemState.appIds) {
+        if (appId in newState.systemState.appIdPackageNames) {
             trimPermissionStates(appId)
         }
         changedPermissionNames.forEachIndexed { _, permissionName ->
@@ -256,7 +259,7 @@
 
     private fun MutateStateScope.adoptPermissions(
         packageState: PackageState,
-        changedPermissionNames: IndexedSet<String>
+        changedPermissionNames: MutableIndexedSet<String>
     ) {
         val `package` = packageState.androidPackage!!
         `package`.adoptPermissions.forEachIndexed { _, originalPackageName ->
@@ -264,9 +267,7 @@
             if (!canAdoptPermissions(packageName, originalPackageName)) {
                 return@forEachIndexed
             }
-            val systemState = newState.systemState
-            val permissions = systemState.permissions
-            permissions.forEachIndexed permissions@ {
+            newState.systemState.permissions.forEachIndexed permissions@ {
                 permissionIndex, permissionName, oldPermission ->
                 if (oldPermission.packageName != originalPackageName) {
                     return@permissions
@@ -283,8 +284,8 @@
                 val newPermission = oldPermission.copy(
                     permissionInfo = newPermissionInfo, isReconciled = false, appId = 0
                 )
-                permissions.setValueAt(permissionIndex, newPermission)
-                systemState.requestWrite()
+                newState.mutateSystemState().mutatePermissions()
+                    .putAt(permissionIndex, newPermission)
                 changedPermissionNames += permissionName
             }
         }
@@ -362,13 +363,14 @@
                         " declared in another package $oldPackageName"
                 )
             }
-            newState.systemState.permissionGroups[permissionGroupName] = newPermissionGroup
+            newState.mutateSystemState().mutatePermissionGroups()[permissionGroupName] =
+                newPermissionGroup
         }
     }
 
     private fun MutateStateScope.addPermissions(
         packageState: PackageState,
-        changedPermissionNames: IndexedSet<String>
+        changedPermissionNames: MutableIndexedSet<String>
     ) {
         packageState.androidPackage!!.permissions.forEachIndexed { _, parsedPermission ->
             // TODO:
@@ -383,12 +385,11 @@
                 parsedPermission, PackageManager.GET_META_DATA.toLong()
             )!!
             // TODO: newPermissionInfo.flags |= PermissionInfo.FLAG_INSTALLED
-            val systemState = newState.systemState
             val permissionName = newPermissionInfo.name
             val oldPermission = if (parsedPermission.isTree) {
-                systemState.permissionTrees[permissionName]
+                newState.systemState.permissionTrees[permissionName]
             } else {
-                systemState.permissions[permissionName]
+                newState.systemState.permissions[permissionName]
             }
             // Different from the old implementation, which may add an (incomplete) signature
             // permission inside another package's permission tree, we now consistently ignore such
@@ -421,15 +422,15 @@
                         permissionInfo = newPermissionInfo, isReconciled = true,
                         appId = packageState.appId
                     )
-                } else if (systemState.packageStates[oldPackageName]?.isSystem != true) {
+                } else if (newState.systemState.packageStates[oldPackageName]?.isSystem != true) {
                     Log.w(
                         LOG_TAG, "Overriding permission $permissionName with new declaration in" +
                             " system package $newPackageName: originally declared in another" +
                             " package $oldPackageName"
                     )
                     // Remove permission state on owner change.
-                    systemState.userIds.forEachIndexed { _, userId ->
-                        systemState.appIds.forEachKeyIndexed { _, appId ->
+                    newState.systemState.userIds.forEachIndexed { _, userId ->
+                        newState.systemState.appIdPackageNames.forEachIndexed { _, appId, _ ->
                             setPermissionFlags(appId, userId, permissionName, 0)
                         }
                     }
@@ -458,8 +459,8 @@
                             (newPermissionInfo.isInternal && !oldPermission.isInternal)
                     )
                     if (isPermissionGroupChanged || isPermissionTypeChanged) {
-                        systemState.userIds.forEachIndexed { _, userId ->
-                            systemState.appIds.forEachKeyIndexed { _, appId ->
+                        newState.systemState.userIds.forEachIndexed { _, userId ->
+                            newState.systemState.appIdPackageNames.forEachIndexed { _, appId, _ ->
                                 if (isPermissionGroupChanged) {
                                     // We might auto-grant permissions if any permission of
                                     // the group is already granted. Hence if the group of
@@ -502,32 +503,30 @@
             }
 
             if (parsedPermission.isTree) {
-                systemState.permissionTrees[permissionName] = newPermission
+                newState.mutateSystemState().mutatePermissionTrees()[permissionName] = newPermission
             } else {
-                systemState.permissions[permissionName] = newPermission
+                newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission
             }
-            systemState.requestWrite()
             changedPermissionNames += permissionName
         }
     }
 
     private fun MutateStateScope.trimPermissions(
         packageName: String,
-        changedPermissionNames: IndexedSet<String>
+        changedPermissionNames: MutableIndexedSet<String>
     ) {
-        val systemState = newState.systemState
-        val packageState = systemState.packageStates[packageName]
+        val packageState = newState.systemState.packageStates[packageName]
         val androidPackage = packageState?.androidPackage
         if (packageState != null && androidPackage == null) {
             return
         }
-        val disabledSystemPackage = systemState.disabledSystemPackageStates[packageName]
+        val disabledSystemPackage = newState.systemState.disabledSystemPackageStates[packageName]
             ?.androidPackage
         // Unlike in the previous implementation, we now also retain permission trees defined by
         // disabled system packages for consistency with permissions.
-        val isPermissionTreeRemoved = systemState.permissionTrees.removeAllIndexed {
-            _, permissionTreeName, permissionTree ->
-            permissionTree.packageName == packageName && (
+        newState.systemState.permissionTrees.forEachReversedIndexed {
+            permissionTreeIndex, permissionTreeName, permissionTree ->
+            if (permissionTree.packageName == packageName && (
                 packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
                     it.isTree && it.name == permissionTreeName
                 }
@@ -535,15 +534,16 @@
                 disabledSystemPackage?.permissions?.anyIndexed { _, it ->
                     it.isTree && it.name == permissionTreeName
                 } != true
-            )
-        }
-        if (isPermissionTreeRemoved) {
-            systemState.requestWrite()
+            )) {
+                newState.mutateSystemState().mutatePermissionTrees().removeAt(permissionTreeIndex)
+            }
         }
 
-        systemState.permissions.removeAllIndexed { permissionIndex, permissionName, permission ->
+        newState.systemState.permissions.forEachReversedIndexed {
+            permissionIndex, permissionName, permission ->
             val updatedPermission = updatePermissionIfDynamic(permission)
-            newState.systemState.permissions.setValueAt(permissionIndex, updatedPermission)
+            newState.mutateSystemState().mutatePermissions()
+                .putAt(permissionIndex, updatedPermission)
             if (updatedPermission.packageName == packageName && (
                 packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
                     !it.isTree && it.name == permissionName
@@ -558,16 +558,13 @@
                 // shouldn't be notified when the updated system package is removed but the disabled
                 // system package isn't re-enabled yet, so we don't need to maintain that brittle
                 // special case either.
-                systemState.userIds.forEachIndexed { _, userId ->
-                    systemState.appIds.forEachKeyIndexed { _, appId ->
+                newState.systemState.userIds.forEachIndexed { _, userId ->
+                    newState.systemState.appIdPackageNames.forEachIndexed { _, appId, _ ->
                         setPermissionFlags(appId, userId, permissionName, 0)
                     }
                 }
+                newState.mutateSystemState().mutatePermissions().removeAt(permissionIndex)
                 changedPermissionNames += permissionName
-                systemState.requestWrite()
-                true
-            } else {
-                false
             }
         }
     }
@@ -586,7 +583,7 @@
     }
 
     private fun MutateStateScope.trimPermissionStates(appId: Int) {
-        val requestedPermissions = IndexedSet<String>()
+        val requestedPermissions = MutableIndexedSet<String>()
         forEachPackageInAppId(appId) {
             // Note that we still trim the permission states requested by disabled system packages.
             // Because in the previous implementation:
@@ -648,7 +645,7 @@
     ) {
         val systemState = newState.systemState
         systemState.userIds.forEachIndexed { _, userId ->
-            systemState.appIds.forEachKeyIndexed { _, appId ->
+            systemState.appIdPackageNames.forEachIndexed { _, appId, _ ->
                 val isPermissionRequested =
                     anyRequestingPackageInAppId(appId, permissionName) { true }
                 if (isPermissionRequested) {
@@ -687,7 +684,7 @@
         permissionName: String,
         installedPackageState: PackageState?
     ) {
-        val packageNames = newState.systemState.appIds[appId]
+        val packageNames = newState.systemState.appIdPackageNames[appId]!!
         val hasMissingPackage = packageNames.anyIndexed { _, packageName ->
             newState.systemState.packageStates[packageName]!!.androidPackage == null
         }
@@ -803,10 +800,11 @@
                 }
                 val sourcePermissions = newState.systemState
                     .implicitToSourcePermissions[permissionName]
-                val isAnySourcePermissionNonRuntime = sourcePermissions?.any {
-                    val sourcePermission = newState.systemState.permissions[it]
+                val isAnySourcePermissionNonRuntime = sourcePermissions?.anyIndexed {
+                    _, sourcePermissionName ->
+                    val sourcePermission = newState.systemState.permissions[sourcePermissionName]
                     checkNotNull(sourcePermission) {
-                        "Unknown source permission $it in split permissions"
+                        "Unknown source permission $sourcePermissionName in split permissions"
                     }
                     !sourcePermission.isRuntime
                 } ?: false
@@ -894,7 +892,7 @@
     }
 
     private fun MutateStateScope.inheritImplicitPermissionStates(appId: Int, userId: Int) {
-        val implicitPermissions = IndexedSet<String>()
+        val implicitPermissions = MutableIndexedSet<String>()
         forEachPackageInAppId(appId) {
             implicitPermissions += it.androidPackage!!.implicitPermissions
         }
@@ -1086,7 +1084,7 @@
         state: AccessState = newState,
         predicate: (PackageState) -> Boolean
     ): Boolean {
-        val packageNames = state.systemState.appIds[appId]
+        val packageNames = state.systemState.appIdPackageNames[appId]!!
         return packageNames.anyIndexed { _, packageName ->
             val packageState = state.systemState.packageStates[packageName]!!
             val androidPackage = packageState.androidPackage
@@ -1100,7 +1098,7 @@
         state: AccessState = newState,
         action: (PackageState) -> Unit
     ) {
-        val packageNames = state.systemState.appIds[appId]!!
+        val packageNames = state.systemState.appIdPackageNames[appId]!!
         packageNames.forEachIndexed { _, packageName ->
             val packageState = state.systemState.packageStates[packageName]!!
             if (packageState.androidPackage != null) {
@@ -1115,7 +1113,7 @@
         state: AccessState = newState,
         action: (PackageState) -> Unit
     ) {
-        val packageNames = state.systemState.appIds[appId]
+        val packageNames = state.systemState.appIdPackageNames[appId]!!
         packageNames.forEachIndexed { _, packageName ->
             val packageState = state.systemState.packageStates[packageName]!!
             val androidPackage = packageState.androidPackage
@@ -1156,15 +1154,15 @@
             return true
         }
         if (permission.isInstaller && (
-            packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER] ||
-                packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER]
+            packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER]!! ||
+                packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER]!!
         )) {
             // If this permission is to be granted to the system installer and
             // this app is an installer or permission controller, then it gets the permission.
             return true
         }
         if (permission.isVerifier &&
-            packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]) {
+            packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]!!) {
             // If this permission is to be granted to the system verifier and
             // this app is a verifier, then it gets the permission.
             return true
@@ -1180,39 +1178,39 @@
             return true
         }
         if (permission.isSetup &&
-            packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]) {
+            packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]!!) {
             // If this permission is to be granted to the system setup wizard and
             // this app is a setup wizard, then it gets the permission.
             return true
         }
         if (permission.isSystemTextClassifier &&
-            packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]) {
+            packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]!!) {
             // Special permissions for the system default text classifier.
             return true
         }
         if (permission.isConfigurator &&
-            packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]) {
+            packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]!!) {
             // Special permissions for the device configurator.
             return true
         }
         if (permission.isIncidentReportApprover &&
-            packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]) {
+            packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]!!) {
             // If this permission is to be granted to the incident report approver and
             // this app is the incident report approver, then it gets the permission.
             return true
         }
         if (permission.isAppPredictor &&
-            packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]) {
+            packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]!!) {
             // Special permissions for the system app predictor.
             return true
         }
         if (permission.isCompanion &&
-            packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]) {
+            packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]!!) {
             // Special permissions for the system companion device manager.
             return true
         }
         if (permission.isRetailDemo &&
-            packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO] &&
+            packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO]!! &&
             isDeviceOrProfileOwnerUid(packageState.appId)) {
             // Special permission granted only to the OEM specified retail demo app.
             // Note that the original code was passing app ID as UID, so this behavior is kept
@@ -1220,7 +1218,7 @@
             return true
         }
         if (permission.isRecents &&
-            packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]) {
+            packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]!!) {
             // Special permission for the recents app.
             return true
         }
@@ -1284,7 +1282,7 @@
         }
     }
 
-    override fun BinaryXmlPullParser.parseSystemState(state: AccessState) {
+    override fun BinaryXmlPullParser.parseSystemState(state: MutableAccessState) {
         with(persistence) { this@parseSystemState.parseSystemState(state) }
     }
 
@@ -1292,7 +1290,7 @@
         with(persistence) { this@serializeSystemState.serializeSystemState(state) }
     }
 
-    override fun BinaryXmlPullParser.parseUserState(state: AccessState, userId: Int) {
+    override fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
         with(persistence) { this@parseUserState.parseUserState(state, userId) }
     }
 
@@ -1316,8 +1314,7 @@
         }
 
     fun MutateStateScope.addPermissionTree(permission: Permission) {
-        newState.systemState.permissionTrees[permission.name] = permission
-        newState.systemState.requestWrite()
+        newState.mutateSystemState().mutatePermissionTrees()[permission.name] = permission
     }
 
     /**
@@ -1332,14 +1329,16 @@
     fun GetStateScope.getPermissions(): IndexedMap<String, Permission> =
         state.systemState.permissions
 
-    fun MutateStateScope.addPermission(permission: Permission, sync: Boolean = false) {
-        newState.systemState.permissions[permission.name] = permission
-        newState.systemState.requestWrite(sync)
+    fun MutateStateScope.addPermission(
+        permission: Permission,
+        isSynchronousWrite: Boolean = false
+    ) {
+        val writeMode = if (isSynchronousWrite) WriteMode.SYNCHRONOUS else WriteMode.ASYNCHRONOUS
+        newState.mutateSystemState(writeMode).mutatePermissions()[permission.name] = permission
     }
 
     fun MutateStateScope.removePermission(permission: Permission) {
-        newState.systemState.permissions -= permission.name
-        newState.systemState.requestWrite()
+        newState.mutateSystemState().mutatePermissions() -= permission.name
     }
 
     fun GetStateScope.getUidPermissionFlags(appId: Int, userId: Int): IndexedMap<String, Int>? =
@@ -1380,23 +1379,18 @@
         flagMask: Int,
         flagValues: Int
     ): Boolean {
-        val userState = newState.userStates[userId]
-        val appIdPermissionFlags = userState.appIdPermissionFlags
-        var permissionFlags = appIdPermissionFlags[appId]
-        val oldFlags = permissionFlags.getWithDefault(permissionName, 0)
+        val oldFlags = newState.userStates[userId]!!.appIdPermissionFlags[appId]
+            .getWithDefault(permissionName, 0)
         val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask)
         if (oldFlags == newFlags) {
             return false
         }
-        if (permissionFlags == null) {
-            permissionFlags = IndexedMap()
-            appIdPermissionFlags[appId] = permissionFlags
-        }
+        val appIdPermissionFlags = newState.mutateUserState(userId)!!.mutateAppIdPermissionFlags()
+        val permissionFlags = appIdPermissionFlags.mutateOrPut(appId) { MutableIndexedMap() }
         permissionFlags.putWithDefault(permissionName, newFlags, 0)
         if (permissionFlags.isEmpty()) {
             appIdPermissionFlags -= appId
         }
-        userState.requestWrite()
         onPermissionFlagsChangedListeners.forEachIndexed { _, it ->
             it.onPermissionFlagsChanged(appId, userId, permissionName, oldFlags, newFlags)
         }
@@ -1415,11 +1409,11 @@
         }
     }
 
-    override fun migrateSystemState(state: AccessState) {
+    override fun migrateSystemState(state: MutableAccessState) {
         migration.migrateSystemState(state)
     }
 
-    override fun migrateUserState(state: AccessState, userId: Int) {
+    override fun migrateUserState(state: MutableAccessState, userId: Int) {
         migration.migrateUserState(state, userId)
     }
 
@@ -1428,7 +1422,7 @@
         userId: Int,
         version: Int
     ) {
-        with(upgrade) { upgradePermissions(packageState, userId, version) }
+        with(upgrade) { upgradePackageState(packageState, userId, version) }
     }
 
     companion object {
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
index 9e70800..b17a7d8 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
@@ -21,6 +21,7 @@
 import android.util.Log
 import com.android.server.permission.access.MutateStateScope
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.util.andInv
 import com.android.server.permission.access.util.hasAnyBit
 import com.android.server.permission.access.util.hasBits
@@ -34,7 +35,7 @@
      *
      * @see [com.android.server.permission.access.util.PackageVersionMigration.getVersion]
      */
-    fun MutateStateScope.upgradePermissions(
+    fun MutateStateScope.upgradePackageState(
         packageState: PackageState,
         userId: Int,
         version: Int
@@ -93,15 +94,18 @@
         packageState: PackageState,
         userId: Int
     ) {
-        val androidPackage = packageState.androidPackage!!
-        if (Manifest.permission.ACCESS_BACKGROUND_LOCATION in androidPackage.requestedPermissions) {
-            val permissionFlags =
-                state.userStates[userId].appIdPermissionFlags[packageState.appId]
-            val fineLocationFlags = permissionFlags[Manifest.permission.ACCESS_FINE_LOCATION] ?: 0
-            val coarseLocationFlags =
-                permissionFlags[Manifest.permission.ACCESS_COARSE_LOCATION] ?: 0
-            val isForegroundLocationGranted = PermissionFlags.isAppOpGranted(fineLocationFlags) ||
-                PermissionFlags.isAppOpGranted(coarseLocationFlags)
+        if (Manifest.permission.ACCESS_BACKGROUND_LOCATION in
+            packageState.androidPackage!!.requestedPermissions) {
+            val appId = packageState.appId
+            val accessFineLocationFlags = with(policy) {
+                getPermissionFlags(appId, userId, Manifest.permission.ACCESS_FINE_LOCATION)
+            }
+            val accessCoarseLocationFlags = with(policy) {
+                getPermissionFlags(appId, userId, Manifest.permission.ACCESS_COARSE_LOCATION)
+            }
+            val isForegroundLocationGranted =
+                PermissionFlags.isAppOpGranted(accessFineLocationFlags) ||
+                    PermissionFlags.isAppOpGranted(accessCoarseLocationFlags)
             if (isForegroundLocationGranted) {
                 grantRuntimePermission(
                     packageState, userId, Manifest.permission.ACCESS_BACKGROUND_LOCATION
@@ -114,10 +118,13 @@
         packageState: PackageState,
         userId: Int
     ) {
-        val androidPackage = packageState.androidPackage!!
-        if (Manifest.permission.ACCESS_MEDIA_LOCATION in androidPackage.requestedPermissions) {
-            val permissionFlags = state.userStates[userId].appIdPermissionFlags[packageState.appId]
-            val flags = permissionFlags[Manifest.permission.READ_EXTERNAL_STORAGE] ?: 0
+        if (Manifest.permission.ACCESS_MEDIA_LOCATION in
+            packageState.androidPackage!!.requestedPermissions) {
+            val flags = with(policy) {
+                getPermissionFlags(
+                    packageState.appId, userId, Manifest.permission.READ_EXTERNAL_STORAGE
+                )
+            }
             if (PermissionFlags.isAppOpGranted(flags)) {
                 grantRuntimePermission(
                     packageState, userId, Manifest.permission.ACCESS_MEDIA_LOCATION
@@ -134,17 +141,19 @@
         if (androidPackage.targetSdkVersion < Build.VERSION_CODES.TIRAMISU) {
             return
         }
-
         val requestedPermissionNames = androidPackage.requestedPermissions
-        val permissionFlags = state.userStates[userId].appIdPermissionFlags[packageState.appId]
-        val isStorageUserGranted = requestedPermissionNames.anyIndexed { _, permissionName ->
-            val flags = permissionFlags[permissionName] ?: 0
-            permissionName in STORAGE_PERMISSIONS && PermissionFlags.isAppOpGranted(flags) &&
-                flags.hasBits(PermissionFlags.USER_SET)
+        val isStorageUserGranted = STORAGE_PERMISSIONS.anyIndexed { _, permissionName ->
+            if (permissionName !in requestedPermissionNames) {
+                return@anyIndexed false
+            }
+            val flags = with(policy) {
+                getPermissionFlags(packageState.appId, userId, permissionName)
+            }
+            PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET)
         }
         if (isStorageUserGranted) {
-            requestedPermissionNames.forEachIndexed { _, permissionName ->
-                if (permissionName in AURAL_VISUAL_MEDIA_PERMISSIONS) {
+            AURAL_VISUAL_MEDIA_PERMISSIONS.forEachIndexed { _, permissionName ->
+                if (permissionName in requestedPermissionNames) {
                     grantRuntimePermission(packageState, userId, permissionName)
                 }
             }
@@ -160,14 +169,13 @@
             LOG_TAG, "Granting runtime permission for package: ${packageState.packageName}, " +
                 "permission: $permissionName, userId: $userId"
         )
-        val permission = state.systemState.permissions[permissionName]!!
+        val permission = newState.systemState.permissions[permissionName]!!
         if (packageState.getUserStateOrDefault(userId).isInstantApp && !permission.isInstant) {
             return
         }
 
         val appId = packageState.appId
-        val permissionFlags = state.userStates[userId].appIdPermissionFlags[appId]
-        var flags = permissionFlags[permission.name] ?: 0
+        var flags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
         if (flags.hasAnyBit(MASK_ANY_FIXED)) {
             Log.v(
                 LOG_TAG,
@@ -184,9 +192,7 @@
             PermissionFlags.HIBERNATION or
             PermissionFlags.ONE_TIME
         )
-        with(policy) {
-            setPermissionFlags(appId, userId, permissionName, flags)
-        }
+        with(policy) { setPermissionFlags(appId, userId, permissionName, flags) }
     }
 
     companion object {
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 9146a05..f3bb2b9 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -45,9 +45,12 @@
 import android.permission.PermissionControllerManager
 import android.permission.PermissionManager
 import android.provider.Settings
+import android.util.ArrayMap
+import android.util.ArraySet
 import android.util.DebugUtils
 import android.util.IntArray as GrowingIntArray
 import android.util.Log
+import android.util.SparseBooleanArray
 import com.android.internal.compat.IPlatformCompat
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.nano.MetricsProto
@@ -67,6 +70,7 @@
 import com.android.server.permission.access.UidUri
 import com.android.server.permission.access.appop.AppIdAppOpPolicy
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.util.andInv
 import com.android.server.permission.access.util.hasAnyBit
 import com.android.server.permission.access.util.hasBits
@@ -117,7 +121,7 @@
     private lateinit var onPermissionsChangeListeners: OnPermissionsChangeListeners
     private lateinit var onPermissionFlagsChangedListener: OnPermissionFlagsChangedListener
 
-    private val mountedStorageVolumes = IndexedSet<String?>()
+    private val mountedStorageVolumes = ArraySet<String?>()
 
     private lateinit var permissionControllerManager: PermissionControllerManager
 
@@ -128,7 +132,7 @@
      * This array (`userId -> noDelayedBackupLeft`) is `true` for all the users where
      * there is **no more** delayed backup left.
      */
-    private val isDelayedPermissionBackupFinished = IntBooleanMap()
+    private val isDelayedPermissionBackupFinished = SparseBooleanArray()
 
     fun initialize() {
         metricsLogger = MetricsLogger()
@@ -145,8 +149,8 @@
         // The package info cache is the cache for package and permission information.
         // Disable the package info and package permission caches locally but leave the
         // checkPermission cache active.
-        PackageManager.invalidatePackageInfoCache();
-        PermissionManager.disablePackageNamePermissionCache();
+        PackageManager.invalidatePackageInfoCache()
+        PermissionManager.disablePackageNamePermissionCache()
 
         handlerThread = ServiceThread(LOG_TAG, Process.THREAD_PRIORITY_BACKGROUND, true)
             .apply { start() }
@@ -167,7 +171,7 @@
                 with(policy) { getPermissionGroups() }
             }
 
-            return permissionGroups.mapNotNullIndexed { _, _, permissionGroup ->
+            return permissionGroups.mapNotNullIndexedTo(ArrayList()) { _, _, permissionGroup ->
                 if (snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) {
                     permissionGroup.generatePermissionGroupInfo(flags)
                 } else {
@@ -278,8 +282,7 @@
                 return null
             }
 
-            val permissions: IndexedMap<String, Permission>
-            service.getState {
+            val permissions = service.getState {
                 if (permissionGroupName != null) {
                     val permissionGroup =
                         with(policy) { getPermissionGroups()[permissionGroupName] } ?: return null
@@ -289,10 +292,10 @@
                     }
                 }
 
-                permissions = with(policy) { getPermissions() }
+                with(policy) { getPermissions() }
             }
 
-            return permissions.mapNotNullIndexed { _, _, permission ->
+            return permissions.mapNotNullIndexedTo(ArrayList()) { _, _, permission ->
                 if (permission.groupName == permissionGroupName &&
                     snapshot.isPackageVisibleToUid(permission.packageName, callingUid)
                 ) {
@@ -317,15 +320,15 @@
     private inline fun getPermissionsWithProtectionOrProtectionFlags(
         predicate: (Permission) -> Boolean
     ): List<PermissionInfo> {
-        service.getState {
-            with(policy) {
-                return getPermissions().mapNotNullIndexed { _, _, permission ->
-                    if (predicate(permission)) {
-                        permission.generatePermissionInfo(0)
-                    } else {
-                        null
-                    }
-                }
+        val permissions = service.getState {
+            with(policy) { getPermissions() }
+        }
+
+        return permissions.mapNotNullIndexedTo(ArrayList()) { _, _, permission ->
+            if (predicate(permission)) {
+                permission.generatePermissionInfo(0)
+            } else {
+                null
             }
         }
     }
@@ -343,7 +346,8 @@
         val permissions = service.getState {
             with(policy) { getPermissions() }
         }
-        return permissions.mapNotNullIndexedToSet { _, _, permission ->
+
+        return permissions.mapNotNullIndexedTo(ArraySet()) { _, _, permission ->
             if (permission.packageName == packageName) {
                 permission.name
             } else {
@@ -440,7 +444,7 @@
     private fun GetStateScope.calculatePermissionTreeFootprint(permissionTree: Permission): Int {
         var size = 0
         with(policy) {
-            getPermissions().forEachValueIndexed { _, permission ->
+            getPermissions().forEachIndexed { _, _, permission ->
                 if (permissionTree.appId == permission.appId) {
                     size += permission.footprint
                 }
@@ -589,7 +593,7 @@
             val permissionFlags = with(policy) { getUidPermissionFlags(packageState.appId, userId) }
                 ?: return emptySet()
 
-            return permissionFlags.mapNotNullIndexedToSet { _, permissionName, _ ->
+            return permissionFlags.mapNotNullIndexedTo(ArraySet()) { _, permissionName, _ ->
                 if (isPermissionGranted(packageState, userId, permissionName)) {
                     permissionName
                 } else {
@@ -746,7 +750,7 @@
     private fun setRequestedPermissionStates(
         packageState: PackageState,
         userId: Int,
-        permissionStates: IndexedMap<String, Int>
+        permissionStates: ArrayMap<String, Int>
     ) {
         service.mutateState {
             permissionStates.forEachIndexed { _, permissionName, permissionState ->
@@ -1298,7 +1302,7 @@
         packageName: String,
         allowlistedFlags: Int,
         userId: Int
-    ): IndexedList<String>? {
+    ): ArrayList<String>? {
         requireNotNull(packageName) { "packageName cannot be null" }
         Preconditions.checkFlagsArgument(allowlistedFlags, PERMISSION_ALLOWLIST_MASK)
         Preconditions.checkArgumentNonnegative(userId, "userId cannot be null")
@@ -1356,7 +1360,7 @@
         appId: Int,
         allowlistedFlags: Int,
         userId: Int
-    ): IndexedList<String>? {
+    ): ArrayList<String>? {
         val permissionFlags = service.getState {
             with(policy) { getUidPermissionFlags(appId, userId) }
         } ?: return null
@@ -1372,7 +1376,7 @@
             queryFlags = queryFlags or PermissionFlags.INSTALLER_EXEMPT
         }
 
-        return permissionFlags.mapNotNullIndexed { _, permissionName, flags ->
+        return permissionFlags.mapNotNullIndexedTo(ArrayList()) { _, permissionName, flags ->
             if (flags.hasAnyBit(queryFlags)) permissionName else null
         }
     }
@@ -1390,7 +1394,7 @@
 
         val permissionNames = getAllowlistedRestrictedPermissions(
             packageName, allowlistedFlags, userId
-        ) ?: IndexedList(1)
+        ) ?: ArrayList(1)
 
         if (permissionName !in permissionNames) {
             permissionNames += permissionName
@@ -1410,7 +1414,7 @@
         val newPermissionNames = getAllowlistedRestrictedPermissionsUnchecked(appId,
             PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, userId
         )?.let {
-            IndexedSet(permissionNames).apply { this += it }.toList()
+            ArraySet(permissionNames).apply { this += it }.toList()
         } ?: permissionNames
 
         setAllowlistedRestrictedPermissionsUnchecked(androidPackage, appId, newPermissionNames,
@@ -1660,7 +1664,7 @@
 
     override fun getAppOpPermissionPackages(permissionName: String): Array<String> {
         requireNotNull(permissionName) { "permissionName cannot be null" }
-        val packageNames = IndexedSet<String>()
+        val packageNames = ArraySet<String>()
 
         val permission = service.getState {
             with(policy) { getPermissions()[permissionName] }
@@ -1682,7 +1686,7 @@
     }
 
     override fun getAllAppOpPermissionPackages(): Map<String, Set<String>> {
-        val appOpPermissionPackageNames = IndexedMap<String, IndexedSet<String>>()
+        val appOpPermissionPackageNames = ArrayMap<String, ArraySet<String>>()
         val permissions = service.getState { with(policy) { getPermissions() } }
         packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
             snapshot.packageStates.forEach packageStates@{ (_, packageState) ->
@@ -1691,7 +1695,7 @@
                     val permission = permissions[permissionName] ?: return@requestedPermissions
                     if (permission.isAppOp) {
                         val packageNames = appOpPermissionPackageNames
-                            .getOrPut(permissionName) { IndexedSet() }
+                            .getOrPut(permissionName) { ArraySet() }
                         packageNames += androidPackage.packageName
                     }
                 }
@@ -1774,7 +1778,7 @@
     override fun getLegacyPermissions(): List<LegacyPermission> =
         service.getState {
             with(policy) { getPermissions() }
-        }.mapIndexed { _, _, permission ->
+        }.mapIndexedTo(ArrayList()) { _, _, permission ->
             LegacyPermission(
                 permission.permissionInfo, permission.type, permission.appId, permission.gids
             )
@@ -1797,7 +1801,7 @@
     private fun toLegacyPermissions(
         permissions: IndexedMap<String, Permission>
     ): List<LegacyPermission> =
-        permissions.mapIndexed { _, _, permission ->
+        permissions.mapIndexedTo(ArrayList()) { _, _, permission ->
             // We don't need to provide UID and GIDs, which are only retrieved when dumping.
             LegacyPermission(
                 permission.permissionInfo, permission.type, 0, EmptyArray.INT
@@ -2136,13 +2140,13 @@
         AppIdPermissionPolicy.OnPermissionFlagsChangedListener() {
         private var isPermissionFlagsChanged = false
 
-        private val runtimePermissionChangedUids = IntSet()
+        private val runtimePermissionChangedUids = MutableIntSet()
         // Mapping from UID to whether only notifications permissions are revoked.
-        private val runtimePermissionRevokedUids = IntBooleanMap()
-        private val gidsChangedUids = IntSet()
+        private val runtimePermissionRevokedUids = SparseBooleanArray()
+        private val gidsChangedUids = MutableIntSet()
 
         private var isKillRuntimePermissionRevokedUidsSkipped = false
-        private val killRuntimePermissionRevokedUidsReasons = IndexedSet<String>()
+        private val killRuntimePermissionRevokedUidsReasons = ArraySet<String>()
 
         fun MutateStateScope.skipKillRuntimePermissionRevokedUids() {
             isKillRuntimePermissionRevokedUidsSkipped = true
@@ -2177,7 +2181,7 @@
                 if (wasPermissionGranted && !isPermissionGranted) {
                     runtimePermissionRevokedUids[uid] =
                         permissionName in NOTIFICATIONS_PERMISSIONS &&
-                            runtimePermissionRevokedUids.getWithDefault(uid, true)
+                            runtimePermissionRevokedUids.get(uid, true)
                 }
             }
 
@@ -2300,14 +2304,14 @@
         @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
         private val BACKGROUND_RATIONALE_CHANGE_ID = 147316723L
 
-        private val FULLER_PERMISSIONS = IndexedMap<String, String>().apply {
+        private val FULLER_PERMISSIONS = ArrayMap<String, String>().apply {
             this[Manifest.permission.ACCESS_COARSE_LOCATION] =
                 Manifest.permission.ACCESS_FINE_LOCATION
             this[Manifest.permission.INTERACT_ACROSS_USERS] =
                 Manifest.permission.INTERACT_ACROSS_USERS_FULL
         }
 
-        private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(
+        private val NOTIFICATIONS_PERMISSIONS = arraySetOf(
             Manifest.permission.POST_NOTIFICATIONS
         )
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 318067e..ec177c9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -406,7 +406,7 @@
         assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt);
         assertTrue(queue.isRunnable());
         assertEquals(BroadcastProcessQueue.REASON_CACHED, queue.getRunnableAtReason());
-        assertEquals(ProcessList.SCHED_GROUP_BACKGROUND, queue.getPreferredSchedulingGroupLocked());
+        assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
     }
 
     /**
@@ -434,13 +434,13 @@
         queue.setProcessAndUidCached(null, false);
         assertTrue(queue.isRunnable());
         assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime);
-        assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
+        assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
         assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord);
 
         queue.setProcessAndUidCached(null, true);
         assertTrue(queue.isRunnable());
         assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime);
-        assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
+        assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
         assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord);
     }
 
@@ -463,7 +463,8 @@
         assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
 
         // Bumping past barrier makes us now runnable
-        airplaneRecord.terminalCount++;
+        airplaneRecord.setDeliveryState(0, BroadcastRecord.DELIVERY_DELIVERED,
+                "testRunnableAt_Ordered");
         queue.invalidateRunnableAt();
         assertTrue(queue.isRunnable());
         assertNotEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
@@ -1154,6 +1155,41 @@
                 times(1));
     }
 
+    @Test
+    public void testGetPreferredSchedulingGroup() throws Exception {
+        final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+        assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
+
+        final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK)
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(timeTick,
+                List.of(makeMockRegisteredReceiver())), 0, false);
+        assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
+
+        // Make the foreground broadcast as active.
+        queue.makeActiveNextPending();
+        assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
+
+        queue.makeActiveIdle();
+        assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(airplane,
+                List.of(makeMockRegisteredReceiver())), 0, false);
+
+        // Make the background broadcast as active.
+        queue.makeActiveNextPending();
+        assertEquals(ProcessList.SCHED_GROUP_BACKGROUND, queue.getPreferredSchedulingGroupLocked());
+
+        queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(timeTick,
+                List.of(makeMockRegisteredReceiver())), 0, false);
+        // Even though the active broadcast is not a foreground one, scheduling group will be
+        // DEFAULT since there is a foreground broadcast waiting to be delivered.
+        assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
+    }
+
     private Intent createPackageChangedIntent(int uid, List<String> componentNameList) {
         final Intent packageChangedIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
         packageChangedIntent.putExtra(Intent.EXTRA_UID, uid);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index b6bc02a..d7ba3df 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
 import static android.os.UserHandle.USER_SYSTEM;
 
 import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
@@ -39,7 +41,6 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -463,7 +464,7 @@
 
         doAnswer((invocation) -> {
             Log.v(TAG, "Intercepting scheduleReceiver() for "
-                    + Arrays.toString(invocation.getArguments()));
+                    + Arrays.toString(invocation.getArguments()) + " package " + ai.packageName);
             assertHealth();
             final Intent intent = invocation.getArgument(0);
             final Bundle extras = invocation.getArgument(5);
@@ -485,7 +486,7 @@
 
         doAnswer((invocation) -> {
             Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for "
-                    + Arrays.toString(invocation.getArguments()));
+                    + Arrays.toString(invocation.getArguments()) + " package " + ai.packageName);
             assertHealth();
             final Intent intent = invocation.getArgument(1);
             final Bundle extras = invocation.getArgument(4);
@@ -961,7 +962,7 @@
             } else {
                 // Confirm that app was thawed
                 verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
-                        eq(receiverApp), eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER));
+                        eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER));
 
                 // Confirm that we added package to process
                 verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
@@ -1404,7 +1405,7 @@
 
         // Finally, verify that we thawed the final receiver
         verify(mAms.mOomAdjuster).unfreezeTemporarily(eq(callerApp),
-                eq(OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER));
+                eq(OOM_ADJ_REASON_FINISH_RECEIVER));
     }
 
     /**
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 2b6f217..08952ea 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -24,7 +24,12 @@
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
-import static com.android.server.am.BroadcastRecord.calculateBlockedUntilTerminalCount;
+import static com.android.server.am.BroadcastRecord.DELIVERY_DEFERRED;
+import static com.android.server.am.BroadcastRecord.DELIVERY_DELIVERED;
+import static com.android.server.am.BroadcastRecord.DELIVERY_PENDING;
+import static com.android.server.am.BroadcastRecord.DELIVERY_SKIPPED;
+import static com.android.server.am.BroadcastRecord.DELIVERY_TIMEOUT;
+import static com.android.server.am.BroadcastRecord.calculateBlockedUntilBeyondCount;
 import static com.android.server.am.BroadcastRecord.calculateDeferUntilActive;
 import static com.android.server.am.BroadcastRecord.calculateUrgent;
 import static com.android.server.am.BroadcastRecord.isReceiverEquals;
@@ -58,7 +63,6 @@
 import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -79,6 +83,7 @@
 @SmallTest
 @RunWith(MockitoJUnitRunner.class)
 public class BroadcastRecordTest {
+    private static final String TAG = "BroadcastRecordTest";
 
     private static final int USER0 = UserHandle.USER_SYSTEM;
     private static final int USER1 = USER0 + 1;
@@ -120,13 +125,13 @@
         assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 10))));
 
         assertArrayEquals(new int[] {-1},
-                calculateBlockedUntilTerminalCount(List.of(
+                calculateBlockedUntilBeyondCount(List.of(
                         createResolveInfo(PACKAGE1, getAppId(1), 0)), false));
         assertArrayEquals(new int[] {-1},
-                calculateBlockedUntilTerminalCount(List.of(
+                calculateBlockedUntilBeyondCount(List.of(
                         createResolveInfo(PACKAGE1, getAppId(1), -10)), false));
         assertArrayEquals(new int[] {-1},
-                calculateBlockedUntilTerminalCount(List.of(
+                calculateBlockedUntilBeyondCount(List.of(
                         createResolveInfo(PACKAGE1, getAppId(1), 10)), false));
     }
 
@@ -142,12 +147,12 @@
                 createResolveInfo(PACKAGE3, getAppId(3), 10))));
 
         assertArrayEquals(new int[] {-1,-1,-1},
-                calculateBlockedUntilTerminalCount(List.of(
+                calculateBlockedUntilBeyondCount(List.of(
                         createResolveInfo(PACKAGE1, getAppId(1), 0),
                         createResolveInfo(PACKAGE2, getAppId(2), 0),
                         createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
         assertArrayEquals(new int[] {-1,-1,-1},
-                calculateBlockedUntilTerminalCount(List.of(
+                calculateBlockedUntilBeyondCount(List.of(
                         createResolveInfo(PACKAGE1, getAppId(1), 10),
                         createResolveInfo(PACKAGE2, getAppId(2), 10),
                         createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
@@ -156,26 +161,176 @@
     @Test
     public void testIsPrioritized_Yes() {
         assertTrue(isPrioritized(List.of(
-                createResolveInfo(PACKAGE1, getAppId(1), -10),
+                createResolveInfo(PACKAGE1, getAppId(1), 10),
                 createResolveInfo(PACKAGE2, getAppId(2), 0),
-                createResolveInfo(PACKAGE3, getAppId(3), 10))));
+                createResolveInfo(PACKAGE3, getAppId(3), -10))));
         assertTrue(isPrioritized(List.of(
-                createResolveInfo(PACKAGE1, getAppId(1), 0),
+                createResolveInfo(PACKAGE1, getAppId(1), 10),
                 createResolveInfo(PACKAGE2, getAppId(2), 0),
-                createResolveInfo(PACKAGE3, getAppId(3), 10))));
+                createResolveInfo(PACKAGE3, getAppId(3), 0))));
 
         assertArrayEquals(new int[] {0,1,2},
-                calculateBlockedUntilTerminalCount(List.of(
-                        createResolveInfo(PACKAGE1, getAppId(1), -10),
+                calculateBlockedUntilBeyondCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 10),
                         createResolveInfo(PACKAGE2, getAppId(2), 0),
-                        createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
+                        createResolveInfo(PACKAGE3, getAppId(3), -10)), false));
         assertArrayEquals(new int[] {0,0,2,3,3},
-                calculateBlockedUntilTerminalCount(List.of(
-                        createResolveInfo(PACKAGE1, getAppId(1), 0),
-                        createResolveInfo(PACKAGE2, getAppId(2), 0),
+                calculateBlockedUntilBeyondCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 20),
+                        createResolveInfo(PACKAGE2, getAppId(2), 20),
                         createResolveInfo(PACKAGE3, getAppId(3), 10),
-                        createResolveInfo(PACKAGE3, getAppId(3), 20),
-                        createResolveInfo(PACKAGE3, getAppId(3), 20)), false));
+                        createResolveInfo(PACKAGE3, getAppId(3), 0),
+                        createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
+    }
+
+    @Test
+    public void testSetDeliveryState_Single() {
+        final BroadcastRecord r = createBroadcastRecord(
+                new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+                        createResolveInfoWithPriority(0)));
+        assertEquals(DELIVERY_PENDING, r.getDeliveryState(0));
+        assertBlocked(r, false);
+        assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+        r.setDeliveryState(0, DELIVERY_DEFERRED, TAG);
+        assertEquals(DELIVERY_DEFERRED, r.getDeliveryState(0));
+        assertBlocked(r, false);
+        assertTerminalDeferredBeyond(r, 0, 1, 1);
+
+        // Identical state change has no effect
+        r.setDeliveryState(0, DELIVERY_DEFERRED, TAG);
+        assertEquals(DELIVERY_DEFERRED, r.getDeliveryState(0));
+        assertBlocked(r, false);
+        assertTerminalDeferredBeyond(r, 0, 1, 1);
+
+        // Moving to terminal state updates counters
+        r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+        assertEquals(DELIVERY_DELIVERED, r.getDeliveryState(0));
+        assertBlocked(r, false);
+        assertTerminalDeferredBeyond(r, 1, 0, 1);
+
+        // Trying to change terminal state has no effect
+        r.setDeliveryState(0, DELIVERY_TIMEOUT, TAG);
+        assertEquals(DELIVERY_DELIVERED, r.getDeliveryState(0));
+        assertBlocked(r, false);
+        assertTerminalDeferredBeyond(r, 1, 0, 1);
+    }
+
+    @Test
+    public void testSetDeliveryState_Unordered() {
+        final BroadcastRecord r = createBroadcastRecord(
+                new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(0)));
+        assertBlocked(r, false, false, false);
+        assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+        // Even though we finish a middle item in the tranche, we're not
+        // "beyond" it because there is still unfinished work before it
+        r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false);
+        assertTerminalDeferredBeyond(r, 1, 0, 0);
+
+        r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false);
+        assertTerminalDeferredBeyond(r, 2, 0, 2);
+
+        r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false);
+        assertTerminalDeferredBeyond(r, 3, 0, 3);
+    }
+
+    @Test
+    public void testSetDeliveryState_Ordered() {
+        final BroadcastRecord r = createOrderedBroadcastRecord(
+                new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(0)));
+        assertBlocked(r, false, true, true);
+        assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+        r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, true);
+        assertTerminalDeferredBeyond(r, 1, 0, 1);
+
+        r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false);
+        assertTerminalDeferredBeyond(r, 2, 0, 2);
+
+        r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false);
+        assertTerminalDeferredBeyond(r, 3, 0, 3);
+    }
+
+    @Test
+    public void testSetDeliveryState_DeferUntilActive() {
+        final BroadcastRecord r = createBroadcastRecord(
+                new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+                        createResolveInfoWithPriority(10),
+                        createResolveInfoWithPriority(10),
+                        createResolveInfoWithPriority(10),
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(-10),
+                        createResolveInfoWithPriority(-10),
+                        createResolveInfoWithPriority(-10)));
+        assertBlocked(r, false, false, false, true, true, true, true, true, true);
+        assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+        r.setDeliveryState(0, DELIVERY_PENDING, TAG);
+        r.setDeliveryState(1, DELIVERY_DEFERRED, TAG);
+        r.setDeliveryState(2, DELIVERY_PENDING, TAG);
+        r.setDeliveryState(3, DELIVERY_DEFERRED, TAG);
+        r.setDeliveryState(4, DELIVERY_DEFERRED, TAG);
+        r.setDeliveryState(5, DELIVERY_DEFERRED, TAG);
+        r.setDeliveryState(6, DELIVERY_DEFERRED, TAG);
+        r.setDeliveryState(7, DELIVERY_PENDING, TAG);
+        r.setDeliveryState(8, DELIVERY_DEFERRED, TAG);
+
+        // Verify deferred counts ratchet up, but we're not "beyond" the first
+        // still-pending receiver
+        assertBlocked(r, false, false, false, true, true, true, true, true, true);
+        assertTerminalDeferredBeyond(r, 0, 6, 0);
+
+        // We're still not "beyond" the first still-pending receiver, even when
+        // we finish a receiver later in the first tranche
+        r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false, true, true, true, true, true, true);
+        assertTerminalDeferredBeyond(r, 1, 6, 0);
+
+        // Completing that last item in first tranche means we now unblock the
+        // second tranche, and since it's entirely deferred, the third traunche
+        // is unblocked too
+        r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false, false, false, false, false, false, false);
+        assertTerminalDeferredBeyond(r, 2, 6, 7);
+
+        // Moving a deferred item in an earlier tranche back to being pending
+        // doesn't change the fact that we've already moved beyond it
+        r.setDeliveryState(1, DELIVERY_PENDING, TAG);
+        assertBlocked(r, false, false, false, false, false, false, false, false, false);
+        assertTerminalDeferredBeyond(r, 2, 5, 7);
+        r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false, false, false, false, false, false, false);
+        assertTerminalDeferredBeyond(r, 3, 5, 7);
+
+        // Completing middle pending item is enough to fast-forward to end
+        r.setDeliveryState(7, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false, false, false, false, false, false, false);
+        assertTerminalDeferredBeyond(r, 4, 5, 9);
+
+        // Moving everyone else directly into a finished state updates all the
+        // terminal counters
+        r.setDeliveryState(3, DELIVERY_SKIPPED, TAG);
+        r.setDeliveryState(4, DELIVERY_SKIPPED, TAG);
+        r.setDeliveryState(5, DELIVERY_SKIPPED, TAG);
+        r.setDeliveryState(6, DELIVERY_SKIPPED, TAG);
+        r.setDeliveryState(8, DELIVERY_SKIPPED, TAG);
+        assertBlocked(r, false, false, false, false, false, false, false, false, false);
+        assertTerminalDeferredBeyond(r, 9, 0, 9);
     }
 
     @Test
@@ -688,6 +843,10 @@
                 : errorMsg.insert(0, "Contains unexpected receiver: ").toString();
     }
 
+    private static ResolveInfo createResolveInfoWithPriority(int priority) {
+        return createResolveInfo(PACKAGE1, getAppId(1), priority);
+    }
+
     private static ResolveInfo createResolveInfo(String packageName, int uid) {
         return createResolveInfo(packageName, uid, 0);
     }
@@ -738,21 +897,40 @@
         return excludedList;
     }
 
+    private BroadcastRecord createBroadcastRecord(Intent intent,
+            List<ResolveInfo> receivers) {
+        return createBroadcastRecord(receivers, USER0, intent, null /* filterExtrasForReceiver */,
+                null /* options */, false);
+    }
+
+    private BroadcastRecord createOrderedBroadcastRecord(Intent intent,
+            List<ResolveInfo> receivers) {
+        return createBroadcastRecord(receivers, USER0, intent, null /* filterExtrasForReceiver */,
+                null /* options */, true);
+    }
+
     private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
             Intent intent) {
         return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */,
-                null /* options */);
+                null /* options */, false);
     }
 
     private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
             Intent intent, BroadcastOptions options) {
         return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */,
-                options);
+                options, false);
     }
 
     private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
             Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
             BroadcastOptions options) {
+        return createBroadcastRecord(receivers, userId, intent, filterExtrasForReceiver,
+                options, false);
+    }
+
+    private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
+            Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+            BroadcastOptions options, boolean ordered) {
         return new BroadcastRecord(
                 mQueue /* queue */,
                 intent,
@@ -774,7 +952,7 @@
                 0 /* resultCode */,
                 null /* resultData */,
                 null /* resultExtras */,
-                false /* serialized */,
+                ordered /* serialized */,
                 false /* sticky */,
                 false /* initialSticky */,
                 userId,
@@ -789,6 +967,20 @@
 
     private static boolean isPrioritized(List<Object> receivers) {
         return BroadcastRecord.isPrioritized(
-                calculateBlockedUntilTerminalCount(receivers, false), false);
+                calculateBlockedUntilBeyondCount(receivers, false), false);
+    }
+
+    private static void assertBlocked(BroadcastRecord r, boolean... blocked) {
+        assertEquals(r.receivers.size(), blocked.length);
+        for (int i = 0; i < blocked.length; i++) {
+            assertEquals("blocked " + i, blocked[i], r.isBlocked(i));
+        }
+    }
+
+    private static void assertTerminalDeferredBeyond(BroadcastRecord r,
+            int expectedTerminalCount, int expectedDeferredCount, int expectedBeyondCount) {
+        assertEquals("terminal", expectedTerminalCount, r.terminalCount);
+        assertEquals("deferred", expectedDeferredCount, r.deferredCount);
+        assertEquals("beyond", expectedBeyondCount, r.beyondCount);
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 485ce33..cda5456 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -38,11 +38,12 @@
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_ACTIVITY;
 import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
 import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ;
 import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
@@ -254,12 +255,13 @@
      * - If there's only one process, then it calls updateOomAdjLocked(ProcessRecord, int).
      * - Otherwise, sets the processes to the LRU and run updateOomAdjLocked(int).
      */
+    @SuppressWarnings("GuardedBy")
     private void updateOomAdj(ProcessRecord... apps) {
         if (apps.length == 1) {
-            sService.mOomAdjuster.updateOomAdjLocked(apps[0], OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
         } else {
             setProcessesToLru(apps);
-            sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
             sService.mProcessList.getLruProcessesLOSP().clear();
         }
     }
@@ -658,7 +660,7 @@
         ServiceRecord s = bindService(app, system,
                 null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
                 PERCEPTIBLE_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
@@ -1226,7 +1228,7 @@
                     mock(IBinder.class));
             client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
 
             assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
         }
@@ -1243,7 +1245,7 @@
                     mock(IBinder.class));
             client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
             doReturn(false).when(wpc).isHeavyWeightProcess();
 
             assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
@@ -1497,7 +1499,7 @@
 
         client2.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(client2, OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(client2, OOM_ADJ_REASON_NONE);
 
         assertEquals(PROCESS_STATE_CACHED_EMPTY, client2.mState.getSetProcState());
         assertEquals(PROCESS_STATE_CACHED_EMPTY, client.mState.getSetProcState());
@@ -1919,7 +1921,7 @@
         doReturn(client2).when(sService).getTopApp();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
 
-        sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(app2, OOM_ADJ_REASON_NONE);
         assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
     }
@@ -2029,7 +2031,7 @@
             setServiceMap(s3, MOCKAPP5_UID, cn3);
             setServiceMap(c2s, MOCKAPP3_UID, cn4);
             app2UidRecord.setIdle(false);
-            sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
             assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                     SCHED_GROUP_DEFAULT);
@@ -2055,7 +2057,7 @@
                     anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
             doNothing().when(sService.mServices)
                     .scheduleServiceTimeoutLocked(any(ProcessRecord.class));
-            sService.mOomAdjuster.updateOomAdjLocked(client1, OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(client1, OOM_ADJ_REASON_NONE);
 
             assertEquals(PROCESS_STATE_CACHED_EMPTY, client1.mState.getSetProcState());
             assertEquals(PROCESS_STATE_SERVICE, app1.mState.getSetProcState());
@@ -2427,7 +2429,7 @@
         app2.mState.setHasShownUi(false);
 
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-ui-services");
         assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj2, "cch-started-services");
@@ -2436,7 +2438,7 @@
         app.mState.setAdjType(null);
         app.mState.setSetAdj(UNKNOWN_ADJ);
         app.mState.setHasShownUi(false);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
 
@@ -2445,7 +2447,7 @@
         app.mState.setAdjType(null);
         app.mState.setSetAdj(UNKNOWN_ADJ);
         s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
 
@@ -2463,7 +2465,7 @@
         s.lastActivity = now;
 
         app.mServices.startService(s);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
         assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2474,7 +2476,7 @@
         app.mState.setSetAdj(UNKNOWN_ADJ);
         app.mState.setHasShownUi(false);
         s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
         assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2482,7 +2484,7 @@
         doReturn(userOther).when(sService.mUserController).getCurrentUserId();
         sService.mOomAdjuster.handleUserSwitchedLocked();
 
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
         assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
         assertProcStates(app2, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index fc503b7..45fefe4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -804,7 +804,7 @@
         when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
         when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
         when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
-        when(logicalDisplayMock.getBrightnessThrottlingDataIdLocked()).thenReturn(
+        when(logicalDisplayMock.getThermalBrightnessThrottlingDataIdLocked()).thenReturn(
                 DisplayDeviceConfig.DEFAULT_ID);
         when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
         when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index c021ef6..d9133a4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -809,7 +809,7 @@
         when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
         when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
         when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
-        when(logicalDisplayMock.getBrightnessThrottlingDataIdLocked()).thenReturn(
+        when(logicalDisplayMock.getThermalBrightnessThrottlingDataIdLocked()).thenReturn(
                 DisplayDeviceConfig.DEFAULT_ID);
         when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
         when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
index 03f667f..df2f59a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
@@ -23,6 +23,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -39,6 +41,7 @@
 import android.os.UserHandle;
 
 import com.android.server.LocalServices;
+import com.android.server.job.controllers.JobStatus;
 import com.android.server.notification.NotificationManagerInternal;
 
 import org.junit.After;
@@ -493,6 +496,44 @@
                         eq(notificationId), eq(UserHandle.getUserId(uid)));
     }
 
+    @Test
+    public void testUserInitiatedJob_hasNotificationFlag() {
+        final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
+        final JobServiceContext jsc = mock(JobServiceContext.class);
+        final JobStatus js = mock(JobStatus.class);
+        js.startedAsUserInitiatedJob = true;
+        doReturn(js).when(jsc).getRunningJobLocked();
+        final Notification notification = createValidNotification();
+        final int uid = 10123;
+        final int pid = 42;
+        final int notificationId = 23;
+
+        coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId, notification,
+                JobService.JOB_END_NOTIFICATION_POLICY_REMOVE);
+        verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid)));
+        assertNotEquals(notification.flags & Notification.FLAG_USER_INITIATED_JOB, 0);
+    }
+
+    @Test
+    public void testNonUserInitiatedJob_doesNotHaveNotificationFlag() {
+        final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
+        final JobServiceContext jsc = mock(JobServiceContext.class);
+        doReturn(mock(JobStatus.class)).when(jsc).getRunningJobLocked();
+        final Notification notification = createValidNotification();
+        final int uid = 10123;
+        final int pid = 42;
+        final int notificationId = 23;
+
+        coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId, notification,
+                JobService.JOB_END_NOTIFICATION_POLICY_REMOVE);
+        verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid)));
+        assertEquals(notification.flags & Notification.FLAG_USER_INITIATED_JOB, 0);
+    }
+
     private Notification createValidNotification() {
         final Notification notification = mock(Notification.class);
         doReturn(mock(Icon.class)).when(notification).getSmallIcon();
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java
new file mode 100644
index 0000000..e1fa8f52
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.gnss;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssAntennaInfoProviderTest {
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock IBinder mBinder;
+    private GnssNative mGnssNative;
+
+    private GnssAntennaInfoProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+                anyInt());
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+        mTestProvider = new GnssAntennaInfoProvider(mGnssNative);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testOnHalStarted() {
+        verify(mGnssNative, times(1)).startAntennaInfoListening();
+    }
+
+    @Test
+    public void testOnHalRestarted() {
+        mTestProvider.onHalRestarted();
+        verify(mGnssNative, times(2)).startAntennaInfoListening();
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
index fd9dfe8..bf96b1d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
@@ -74,7 +74,6 @@
     private @Mock Context mContext;
     private @Mock LocationManagerInternal mInternal;
     private @Mock GnssConfiguration mMockConfiguration;
-    private @Mock GnssNative.GeofenceCallbacks mGeofenceCallbacks;
     private @Mock IGnssMeasurementsListener mListener1;
     private @Mock IGnssMeasurementsListener mListener2;
     private @Mock IBinder mBinder1;
@@ -98,7 +97,6 @@
         Injector injector = new TestInjector(mContext);
         mGnssNative = spy(Objects.requireNonNull(
                 GnssNative.create(injector, mMockConfiguration)));
-        mGnssNative.setGeofenceCallbacks(mGeofenceCallbacks);
         mTestProvider = new GnssMeasurementsProvider(injector, mGnssNative);
         mGnssNative.register();
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java
new file mode 100644
index 0000000..64aa4b3
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.location.gnss;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssNavigationMessageListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+public class GnssNavigationMessageProviderTest {
+    private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+    private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+            "mypackage", "attribution", "listener");
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock IGnssNavigationMessageListener mListener;
+    private @Mock IBinder mBinder;
+
+    private GnssNative mGnssNative;
+
+    private GnssNavigationMessageProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(mBinder).when(mListener).asBinder();
+        doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+                anyInt());
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+        mTestProvider = new GnssNavigationMessageProvider(injector, mGnssNative);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testAddListener() {
+        // add a request
+        mTestProvider.addListener(IDENTITY, mListener);
+        verify(mGnssNative, times(1)).startNavigationMessageCollection();
+
+        // remove a request
+        mTestProvider.removeListener(mListener);
+        verify(mGnssNative, times(1)).stopNavigationMessageCollection();
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java
new file mode 100644
index 0000000..49e5e69
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.location.gnss;
+
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssNmeaListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssNmeaProviderTest {
+
+    private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+    private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+            "mypackage", "attribution", "listener");
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock IGnssNmeaListener mListener;
+    private @Mock IBinder mBinder;
+
+    private GnssNative mGnssNative;
+
+    private GnssNmeaProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(mBinder).when(mListener).asBinder();
+        doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+                anyInt());
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+        mTestProvider = new GnssNmeaProvider(injector, mGnssNative);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testAddListener() {
+        // add a request
+        mTestProvider.addListener(IDENTITY, mListener);
+        verify(mGnssNative, times(1)).startNmeaMessageCollection();
+
+        // remove a request
+        mTestProvider.removeListener(mListener);
+        verify(mGnssNative, times(1)).stopNmeaMessageCollection();
+    }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java
new file mode 100644
index 0000000..ce2aec7f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.location.gnss;
+
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssStatusListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssStatusProviderTest {
+    private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+    private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+            "mypackage", "attribution", "listener");
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock IGnssStatusListener mListener;
+    private @Mock IBinder mBinder;
+
+    private GnssNative mGnssNative;
+
+    private GnssStatusProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(mBinder).when(mListener).asBinder();
+        doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+                anyInt());
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+        mTestProvider = new GnssStatusProvider(injector, mGnssNative);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testAddListener() {
+        // add a request
+        mTestProvider.addListener(IDENTITY, mListener);
+        verify(mGnssNative, times(1)).startSvStatusCollection();
+
+        // remove a request
+        mTestProvider.removeListener(mListener);
+        verify(mGnssNative, times(1)).stopSvStatusCollection();
+    }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
index b7ab6f80..2d962ac 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
@@ -562,6 +562,26 @@
     }
 
     @Override
+    protected boolean startSvStatusCollection() {
+        return true;
+    }
+
+    @Override
+    protected boolean stopSvStatusCollection() {
+        return true;
+    }
+
+    @Override
+    public boolean startNmeaMessageCollection() {
+        return true;
+    }
+
+    @Override
+    public boolean stopNmeaMessageCollection() {
+        return true;
+    }
+
+    @Override
     protected int getBatchSize() {
         return mBatchSize;
     }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 6216c66..4b86dd0 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -152,8 +152,7 @@
 
         verify(mBiometricService, never()).registerAuthenticator(
                 anyInt(),
-                anyInt(),
-                anyInt(),
+                any(),
                 any());
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 4cdca26..26a3ae1 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -43,14 +43,17 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.ITrustManager;
 import android.content.Context;
+import android.content.res.Resources;
 import android.hardware.biometrics.BiometricManager.Authenticators;
-import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorProperties;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Binder;
@@ -82,6 +85,7 @@
     private static final long TEST_REQUEST_ID = 22;
 
     @Mock private Context mContext;
+    @Mock private Resources mResources;
     @Mock private BiometricContext mBiometricContext;
     @Mock private ITrustManager mTrustManager;
     @Mock private DevicePolicyManager mDevicePolicyManager;
@@ -103,6 +107,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        when(mContext.getResources()).thenReturn(mResources);
         when(mClientReceiver.asBinder()).thenReturn(mock(Binder.class));
         when(mBiometricContext.updateContext(any(), anyBoolean()))
                 .thenAnswer(invocation -> invocation.getArgument(0));
@@ -341,6 +346,33 @@
         testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null));
     }
 
+    @Test
+    public void testCallbackOnAcquired() throws RemoteException {
+        final String acquiredStr = "test_acquired_info_callback";
+        final String acquiredStrVendor = "test_acquired_info_callback_vendor";
+        setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR);
+
+        final AuthSession session = createAuthSession(mSensors,
+                false /* checkDevicePolicyManager */,
+                Authenticators.BIOMETRIC_STRONG,
+                TEST_REQUEST_ID,
+                0 /* operationId */,
+                0 /* userId */);
+
+        when(mContext.getString(com.android.internal.R.string.fingerprint_acquired_partial))
+            .thenReturn(acquiredStr);
+        session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL, 0);
+        verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStr));
+        verify(mClientReceiver).onAcquired(eq(1), eq(acquiredStr));
+
+        when(mResources.getStringArray(com.android.internal.R.array.fingerprint_acquired_vendor))
+            .thenReturn(new String[]{acquiredStrVendor});
+        session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, 0);
+        verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStrVendor));
+        verify(mClientReceiver).onAcquired(
+                eq(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR_BASE), eq(acquiredStrVendor));
+    }
+
     // TODO (b/208484275) : Enable these tests
     // @Test
     // public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception {
@@ -458,9 +490,16 @@
         IBiometricAuthenticator fingerprintAuthenticator = mock(IBiometricAuthenticator.class);
         when(fingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
         when(fingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
-        mSensors.add(new BiometricSensor(mContext, id,
+
+        final FingerprintSensorPropertiesInternal props = new FingerprintSensorPropertiesInternal(
+                id, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+                List.of() /* componentInfo */, type,
+                false /* resetLockoutRequiresHardwareAuthToken */);
+        mFingerprintSensorProps.add(props);
+
+        mSensors.add(new BiometricSensor(mContext,
                 TYPE_FINGERPRINT /* modality */,
-                Authenticators.BIOMETRIC_STRONG /* strength */,
+                props,
                 fingerprintAuthenticator) {
             @Override
             boolean confirmationAlwaysRequired(int userId) {
@@ -473,21 +512,6 @@
             }
         });
 
-        final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
-        componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
-                "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
-                "00000001" /* serialNumber */, "" /* softwareVersion */));
-        componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
-                "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
-                "vendor/version/revision" /* softwareVersion */));
-
-        mFingerprintSensorProps.add(new FingerprintSensorPropertiesInternal(id,
-                SensorProperties.STRENGTH_STRONG,
-                5 /* maxEnrollmentsPerUser */,
-                componentInfo,
-                type,
-                false /* resetLockoutRequiresHardwareAuthToken */));
-
         when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
     }
 
@@ -495,9 +519,13 @@
             IBiometricAuthenticator authenticator) throws RemoteException {
         when(authenticator.isHardwareDetected(any())).thenReturn(true);
         when(authenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
-        mSensors.add(new BiometricSensor(mContext, id,
+        mSensors.add(new BiometricSensor(mContext,
                 TYPE_FACE /* modality */,
-                Authenticators.BIOMETRIC_STRONG /* strength */,
+                new FaceSensorPropertiesInternal(id,
+                        SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+                        List.of() /* componentInfo */, FaceSensorProperties.TYPE_UNKNOWN,
+                        true /* supportsFace Detection */, true /* supportsSelfIllumination */,
+                        false /* resetLockoutRequiresHardwareAuthToken */),
                 authenticator) {
             @Override
             boolean confirmationAlwaysRequired(int userId) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 168642e..b51a8c4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -19,6 +19,7 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
 
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
@@ -66,7 +67,11 @@
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -93,6 +98,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
 import java.util.Random;
 
 @Presubmit
@@ -114,6 +120,7 @@
 
     private static final int SENSOR_ID_FINGERPRINT = 0;
     private static final int SENSOR_ID_FACE = 1;
+    private FingerprintSensorPropertiesInternal mFingerprintProps;
 
     private BiometricService mBiometricService;
 
@@ -193,6 +200,11 @@
         };
 
         when(mInjector.getConfiguration(any())).thenReturn(config);
+
+        mFingerprintProps = new FingerprintSensorPropertiesInternal(SENSOR_ID_FINGERPRINT,
+                STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
+                FingerprintSensorProperties.TYPE_UNKNOWN,
+                false /* resetLockoutRequiresHardwareAuthToken */);
     }
 
     @Test
@@ -328,8 +340,7 @@
 
         mBiometricService = new BiometricService(mContext, mInjector);
         mBiometricService.onStart();
-        mBiometricService.mImpl.registerAuthenticator(0 /* id */,
-                TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+        mBiometricService.mImpl.registerAuthenticator(TYPE_FINGERPRINT, mFingerprintProps,
                 mFingerprintAuthenticator);
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -401,8 +412,7 @@
 
         mBiometricService = new BiometricService(mContext, mInjector);
         mBiometricService.onStart();
-        mBiometricService.mImpl.registerAuthenticator(0 /* id */,
-                TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+        mBiometricService.mImpl.registerAuthenticator(TYPE_FINGERPRINT, mFingerprintProps,
                 mFingerprintAuthenticator);
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -1334,9 +1344,13 @@
 
         for (int i = 0; i < testCases.length; i++) {
             final BiometricSensor sensor =
-                    new BiometricSensor(mContext, 0 /* id */,
+                    new BiometricSensor(mContext,
                             TYPE_FINGERPRINT,
-                            testCases[i][0],
+                            new FingerprintSensorPropertiesInternal(i /* id */,
+                                    Utils.authenticatorStrengthToPropertyStrength(testCases[i][0]),
+                                    5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
+                                    FingerprintSensorProperties.TYPE_UNKNOWN,
+                                    false /* resetLockoutRequiresHardwareAuthToken */),
                             mock(IBiometricAuthenticator.class)) {
                         @Override
                         boolean confirmationAlwaysRequired(int userId) {
@@ -1364,8 +1378,7 @@
         when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
                 .thenReturn(true);
         when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
-        mBiometricService.mImpl.registerAuthenticator(0 /* testId */,
-                TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+        mBiometricService.mImpl.registerAuthenticator(TYPE_FINGERPRINT, mFingerprintProps,
                 mFingerprintAuthenticator);
 
         verify(mBiometricService.mBiometricStrengthController).updateStrengths();
@@ -1376,15 +1389,14 @@
         mBiometricService = new BiometricService(mContext, mInjector);
         mBiometricService.onStart();
 
-        final int testId = 0;
-
         when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
 
         when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
                 .thenReturn(true);
         when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
-        mBiometricService.mImpl.registerAuthenticator(testId /* id */,
-                TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+
+        final int testId = SENSOR_ID_FINGERPRINT;
+        mBiometricService.mImpl.registerAuthenticator(TYPE_FINGERPRINT, mFingerprintProps,
                 mFingerprintAuthenticator);
 
         // Downgrade the authenticator
@@ -1484,11 +1496,9 @@
         mBiometricService.onStart();
 
         mBiometricService.mImpl.registerAuthenticator(
-                0 /* id */, 2 /* modality */, 15 /* strength */,
-                mFingerprintAuthenticator);
+                2 /* modality */, mFingerprintProps, mFingerprintAuthenticator);
         mBiometricService.mImpl.registerAuthenticator(
-                0 /* id */, 2 /* modality */, 15 /* strength */,
-                mFingerprintAuthenticator);
+                2 /* modality */, mFingerprintProps, mFingerprintAuthenticator);
     }
 
     @Test(expected = IllegalArgumentException.class)
@@ -1498,9 +1508,7 @@
         mBiometricService.onStart();
 
         mBiometricService.mImpl.registerAuthenticator(
-                0 /* id */, 2 /* modality */,
-                Authenticators.BIOMETRIC_STRONG /* strength */,
-                null /* authenticator */);
+                2 /* modality */, mFingerprintProps, null /* authenticator */);
     }
 
     @Test
@@ -1511,8 +1519,13 @@
 
         for (String s : mInjector.getConfiguration(null)) {
             SensorConfig config = new SensorConfig(s);
-            mBiometricService.mImpl.registerAuthenticator(config.id, config.modality,
-                config.strength, mFingerprintAuthenticator);
+            mBiometricService.mImpl.registerAuthenticator(config.modality,
+                    new FingerprintSensorPropertiesInternal(config.id,
+                            Utils.authenticatorStrengthToPropertyStrength(config.strength),
+                            5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
+                            FingerprintSensorProperties.TYPE_UNKNOWN,
+                            false /* resetLockoutRequiresHardwareAuthToken */),
+                    mFingerprintAuthenticator);
         }
     }
 
@@ -1609,7 +1622,12 @@
             when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
             when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
                     .thenReturn(LockoutTracker.LOCKOUT_NONE);
-            mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FINGERPRINT, modality, strength,
+            mBiometricService.mImpl.registerAuthenticator(modality,
+                    new FingerprintSensorPropertiesInternal(SENSOR_ID_FINGERPRINT,
+                            Utils.authenticatorStrengthToPropertyStrength(strength),
+                            5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
+                            FingerprintSensorProperties.TYPE_UNKNOWN,
+                            false /* resetLockoutRequiresHardwareAuthToken */),
                     mFingerprintAuthenticator);
         }
 
@@ -1618,7 +1636,13 @@
             when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
             when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
                     .thenReturn(LockoutTracker.LOCKOUT_NONE);
-            mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality, strength,
+            mBiometricService.mImpl.registerAuthenticator(modality,
+                    new FaceSensorPropertiesInternal(SENSOR_ID_FACE,
+                            Utils.authenticatorStrengthToPropertyStrength(strength),
+                            5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
+                            FaceSensorProperties.TYPE_UNKNOWN, true /* supportsFace Detection */,
+                            true /* supportsSelfIllumination */,
+                            false /* resetLockoutRequiresHardwareAuthToken */),
                     mFaceAuthenticator);
         }
     }
@@ -1641,15 +1665,27 @@
                 when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
                         .thenReturn(true);
                 when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
-                mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FINGERPRINT, modality,
-                        strength, mFingerprintAuthenticator);
+                mBiometricService.mImpl.registerAuthenticator(modality,
+                        new FingerprintSensorPropertiesInternal(SENSOR_ID_FINGERPRINT,
+                                Utils.authenticatorStrengthToPropertyStrength(strength),
+                                5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
+                                FingerprintSensorProperties.TYPE_UNKNOWN,
+                                false /* resetLockoutRequiresHardwareAuthToken */),
+                        mFingerprintAuthenticator);
             }
 
             if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
                 when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
                 when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
-                mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality,
-                        strength, mFaceAuthenticator);
+                mBiometricService.mImpl.registerAuthenticator(modality,
+                        new FaceSensorPropertiesInternal(SENSOR_ID_FACE,
+                                Utils.authenticatorStrengthToPropertyStrength(strength),
+                                5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
+                                FaceSensorProperties.TYPE_UNKNOWN,
+                                true /* supportsFace Detection */,
+                                true /* supportsSelfIllumination */,
+                                false /* resetLockoutRequiresHardwareAuthToken */),
+                        mFaceAuthenticator);
             }
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java
index ee5ab92..f7539bd 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.biometrics;
 
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -27,9 +30,13 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IInvalidationCallback;
+import android.hardware.biometrics.SensorPropertiesInternal;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
@@ -42,6 +49,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.List;
 
 @Presubmit
 @SmallTest
@@ -59,26 +67,54 @@
     public void testCallbackReceived_whenAllStrongSensorsInvalidated() throws Exception {
         final IBiometricAuthenticator authenticator1 = mock(IBiometricAuthenticator.class);
         when(authenticator1.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
-        final TestSensor sensor1 = new TestSensor(mContext, 0 /* id */,
-                BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+        final TestSensor sensor1 = new TestSensor(mContext,
+                BiometricAuthenticator.TYPE_FINGERPRINT,
+                new FingerprintSensorPropertiesInternal(0 /* id */,
+                        STRENGTH_STRONG,
+                        5 /* maxEnrollmentsPerUser */,
+                        List.of() /* componentInfo */,
+                        FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+                        false /* resetLockoutRequiresHardwareAuthToken */),
                 authenticator1);
 
         final IBiometricAuthenticator authenticator2 = mock(IBiometricAuthenticator.class);
         when(authenticator2.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
-        final TestSensor sensor2 = new TestSensor(mContext, 1 /* id */,
-                BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+        final TestSensor sensor2 = new TestSensor(mContext,
+                BiometricAuthenticator.TYPE_FINGERPRINT,
+                new FingerprintSensorPropertiesInternal(1 /* id */,
+                        STRENGTH_STRONG,
+                        5 /* maxEnrollmentsPerUser */,
+                        List.of() /* componentInfo */,
+                        FingerprintSensorProperties.TYPE_REAR,
+                        false /* resetLockoutRequiresHardwareAuthToken */),
                 authenticator2);
 
         final IBiometricAuthenticator authenticator3 = mock(IBiometricAuthenticator.class);
         when(authenticator3.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
-        final TestSensor sensor3 = new TestSensor(mContext, 2 /* id */,
-                BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG,
+        final TestSensor sensor3 = new TestSensor(mContext,
+                BiometricAuthenticator.TYPE_FACE,
+                new FaceSensorPropertiesInternal(2 /* id */,
+                        STRENGTH_STRONG,
+                        5 /* maxEnrollmentsPerUser */,
+                        List.of() /* componentInfo */,
+                        FaceSensorProperties.TYPE_RGB,
+                        true /* supportsFace Detection */,
+                        true /* supportsSelfIllumination */,
+                        false /* resetLockoutRequiresHardwareAuthToken */),
                 authenticator3);
 
         final IBiometricAuthenticator authenticator4 = mock(IBiometricAuthenticator.class);
         when(authenticator4.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
-        final TestSensor sensor4 = new TestSensor(mContext, 3 /* id */,
-                BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_WEAK,
+        final TestSensor sensor4 = new TestSensor(mContext,
+                BiometricAuthenticator.TYPE_FACE,
+                new FaceSensorPropertiesInternal(3 /* id */,
+                        STRENGTH_WEAK,
+                        5 /* maxEnrollmentsPerUser */,
+                        List.of() /* componentInfo */,
+                        FaceSensorProperties.TYPE_IR,
+                        true /* supportsFace Detection */,
+                        true /* supportsSelfIllumination */,
+                        false /* resetLockoutRequiresHardwareAuthToken */),
                 authenticator4);
 
         final ArrayList<BiometricSensor> sensors = new ArrayList<>();
@@ -113,9 +149,9 @@
 
     private static class TestSensor extends BiometricSensor {
 
-        TestSensor(@NonNull Context context, int id, int modality, int strength,
+        TestSensor(@NonNull Context context, int modality, @NonNull SensorPropertiesInternal props,
                 @NonNull IBiometricAuthenticator impl) {
-            super(context, id, modality, strength, impl);
+            super(context, modality, props, impl);
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
index 903ed90..d3f04df 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
@@ -17,8 +17,6 @@
 package com.android.server.biometrics.sensors.face;
 
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
-import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
-import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
 import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
 import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
 
@@ -70,9 +68,7 @@
     @Mock
     private ServiceProvider mProvider2;
     @Captor
-    private ArgumentCaptor<Integer> mIdCaptor;
-    @Captor
-    private ArgumentCaptor<Integer> mStrengthCaptor;
+    private ArgumentCaptor<FaceSensorPropertiesInternal> mPropsCaptor;
 
     private FaceSensorPropertiesInternal mProvider1Props;
     private FaceSensorPropertiesInternal mProvider2Props;
@@ -82,13 +78,13 @@
     public void setup() {
         mProvider1Props = new FaceSensorPropertiesInternal(SENSOR_ID_1,
                 STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */,
-                List.of(), FaceSensorProperties.TYPE_RGB,
+                List.of() /* componentInfo */, FaceSensorProperties.TYPE_RGB,
                 true /* supportsFace Detection */,
                 true /* supportsSelfIllumination */,
                 false /* resetLockoutRequiresHardwareAuthToken */);
         mProvider2Props = new FaceSensorPropertiesInternal(SENSOR_ID_2,
                 STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
-                List.of(), FaceSensorProperties.TYPE_IR,
+                List.of() /* componentInfo */, FaceSensorProperties.TYPE_IR,
                 true /* supportsFace Detection */,
                 true /* supportsSelfIllumination */,
                 false /* resetLockoutRequiresHardwareAuthToken */);
@@ -107,10 +103,9 @@
         assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2);
         assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props);
         verify(mBiometricService, times(2)).registerAuthenticator(
-                mIdCaptor.capture(), eq(TYPE_FACE), mStrengthCaptor.capture(), any());
-        assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2);
-        assertThat(mStrengthCaptor.getAllValues())
-                .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG);
+                eq(TYPE_FACE), mPropsCaptor.capture(), any());
+        assertThat(mPropsCaptor.getAllValues())
+                .containsExactly(mProvider1Props, mProvider2Props);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
index 13c3f64..6e09069 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
@@ -17,8 +17,6 @@
 package com.android.server.biometrics.sensors.fingerprint;
 
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
-import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
 import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
 import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
 
@@ -70,9 +68,7 @@
     @Mock
     private ServiceProvider mProvider2;
     @Captor
-    private ArgumentCaptor<Integer> mIdCaptor;
-    @Captor
-    private ArgumentCaptor<Integer> mStrengthCaptor;
+    private ArgumentCaptor<FingerprintSensorPropertiesInternal> mPropsCaptor;
 
     private FingerprintSensorPropertiesInternal mProvider1Props;
     private FingerprintSensorPropertiesInternal mProvider2Props;
@@ -82,11 +78,11 @@
     public void setup() {
         mProvider1Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_1,
                 STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */,
-                List.of(), FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+                List.of() /* componentInfo */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
                 false /* resetLockoutRequiresHardwareAuthToken */);
         mProvider2Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_2,
                 STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
-                List.of(), FingerprintSensorProperties.TYPE_UNKNOWN,
+                List.of() /* componentInfo */, FingerprintSensorProperties.TYPE_UNKNOWN,
                 false /* resetLockoutRequiresHardwareAuthToken */);
 
         when(mProvider1.getSensorProperties()).thenReturn(List.of(mProvider1Props));
@@ -103,10 +99,9 @@
         assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2);
         assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props);
         verify(mBiometricService, times(2)).registerAuthenticator(
-                mIdCaptor.capture(), eq(TYPE_FINGERPRINT), mStrengthCaptor.capture(), any());
-        assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2);
-        assertThat(mStrengthCaptor.getAllValues())
-                .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG);
+                eq(TYPE_FINGERPRINT), mPropsCaptor.capture(), any());
+        assertThat(mPropsCaptor.getAllValues())
+                .containsExactly(mProvider1Props, mProvider2Props);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index 25a700a..1089c07 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -110,15 +110,17 @@
     private final FingerprintSensorPropertiesInternal mSensorPropsDefault =
             new FingerprintSensorPropertiesInternal(ID_DEFAULT, STRENGTH_STRONG,
                     2 /* maxEnrollmentsPerUser */,
-                    List.of(),
+                    List.of() /* componentInfo */,
                     TYPE_REAR,
                     false /* resetLockoutRequiresHardwareAuthToken */);
     private final FingerprintSensorPropertiesInternal mSensorPropsVirtual =
             new FingerprintSensorPropertiesInternal(ID_VIRTUAL, STRENGTH_STRONG,
                     2 /* maxEnrollmentsPerUser */,
-                    List.of(),
+                    List.of() /* componentInfo */,
                     TYPE_UDFPS_OPTICAL,
                     false /* resetLockoutRequiresHardwareAuthToken */);
+    @Captor
+    private ArgumentCaptor<FingerprintSensorPropertiesInternal> mPropsCaptor;
     private FingerprintService mService;
 
     @Before
@@ -166,7 +168,8 @@
         mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
         waitForRegistration();
 
-        verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
+        verify(mIBiometricService).registerAuthenticator(anyInt(), mPropsCaptor.capture(), any());
+        assertThat(mPropsCaptor.getAllValues()).containsExactly(mSensorPropsDefault);
     }
 
     @Test
@@ -178,7 +181,8 @@
         mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
         waitForRegistration();
 
-        verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
+        verify(mIBiometricService).registerAuthenticator(anyInt(), mPropsCaptor.capture(), any());
+        assertThat(mPropsCaptor.getAllValues()).containsExactly(mSensorPropsVirtual);
     }
 
     @Test
@@ -188,7 +192,8 @@
         mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
         waitForRegistration();
 
-        verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
+        verify(mIBiometricService).registerAuthenticator(anyInt(), mPropsCaptor.capture(), any());
+        assertThat(mPropsCaptor.getAllValues()).containsExactly(mSensorPropsVirtual);
     }
 
     private void waitForRegistration() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
index ce4b438..46956d7 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
@@ -41,8 +41,8 @@
 
 import com.android.internal.os.BackgroundThread;
 import com.android.server.display.BrightnessThrottler.Injector;
-import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData;
-import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
 import com.android.server.display.mode.DisplayModeDirectorTest;
 
 import org.junit.Before;
@@ -93,7 +93,7 @@
     /////////////////
 
     @Test
-    public void testBrightnessThrottlingData() {
+    public void testThermalBrightnessThrottlingData() {
         List<ThrottlingLevel> singleLevel = new ArrayList<>();
         singleLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
 
@@ -120,32 +120,32 @@
                 PowerManager.BRIGHTNESS_MAX + EPSILON));
 
         // Test invalid data
-        BrightnessThrottlingData data;
-        data = BrightnessThrottlingData.create((List<ThrottlingLevel>)null);
+        ThermalBrightnessThrottlingData data;
+        data = ThermalBrightnessThrottlingData.create((List<ThrottlingLevel>) null);
         assertEquals(data, null);
-        data = BrightnessThrottlingData.create(new ArrayList<ThrottlingLevel>());
+        data = ThermalBrightnessThrottlingData.create(new ArrayList<ThrottlingLevel>());
         assertEquals(data, null);
-        data = BrightnessThrottlingData.create(unsortedThermalLevels);
+        data = ThermalBrightnessThrottlingData.create(unsortedThermalLevels);
         assertEquals(data, null);
-        data = BrightnessThrottlingData.create(unsortedBrightnessLevels);
+        data = ThermalBrightnessThrottlingData.create(unsortedBrightnessLevels);
         assertEquals(data, null);
-        data = BrightnessThrottlingData.create(unsortedLevels);
+        data = ThermalBrightnessThrottlingData.create(unsortedLevels);
         assertEquals(data, null);
-        data = BrightnessThrottlingData.create(invalidLevel);
+        data = ThermalBrightnessThrottlingData.create(invalidLevel);
         assertEquals(data, null);
 
         // Test valid data
-        data = BrightnessThrottlingData.create(singleLevel);
+        data = ThermalBrightnessThrottlingData.create(singleLevel);
         assertNotEquals(data, null);
         assertThrottlingLevelsEquals(singleLevel, data.throttlingLevels);
 
-        data = BrightnessThrottlingData.create(validLevels);
+        data = ThermalBrightnessThrottlingData.create(validLevels);
         assertNotEquals(data, null);
         assertThrottlingLevelsEquals(validLevels, data.throttlingLevels);
     }
 
     @Test
-    public void testThrottlingUnsupported() {
+    public void testThermalThrottlingUnsupported() {
         final BrightnessThrottler throttler = createThrottlerUnsupported();
         assertFalse(throttler.deviceSupportsThrottling());
 
@@ -157,13 +157,13 @@
     }
 
     @Test
-    public void testThrottlingSingleLevel() throws Exception {
+    public void testThermalThrottlingSingleLevel() throws Exception {
         final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
             0.25f);
 
         List<ThrottlingLevel> levels = new ArrayList<>();
         levels.add(level);
-        final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
+        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
         final BrightnessThrottler throttler = createThrottlerSupported(data);
         assertTrue(throttler.deviceSupportsThrottling());
 
@@ -212,7 +212,7 @@
     }
 
     @Test
-    public void testThrottlingMultiLevel() throws Exception {
+    public void testThermalThrottlingMultiLevel() throws Exception {
         final ThrottlingLevel levelLo = new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE,
             0.62f);
         final ThrottlingLevel levelHi = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
@@ -221,7 +221,7 @@
         List<ThrottlingLevel> levels = new ArrayList<>();
         levels.add(levelLo);
         levels.add(levelHi);
-        final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
+        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
         final BrightnessThrottler throttler = createThrottlerSupported(data);
         assertTrue(throttler.deviceSupportsThrottling());
 
@@ -292,32 +292,32 @@
         assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
     }
 
-    @Test public void testUpdateThrottlingData() throws Exception {
+    @Test public void testUpdateThermalThrottlingData() throws Exception {
         // Initialise brightness throttling levels
         // Ensure that they are overridden by setting the data through device config.
         final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
                 0.25f);
         List<ThrottlingLevel> levels = new ArrayList<>();
         levels.add(level);
-        final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
-        mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,0.4");
+        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,0.4");
         final BrightnessThrottler throttler = createThrottlerSupported(data);
 
         verify(mThermalServiceMock).registerThermalEventListenerWithType(
                 mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
         final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
-        testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.4f);
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.4f);
 
         // Set new (valid) data from device config
-        mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,0.8");
-        testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f);
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,0.8");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f);
 
-        mDeviceConfigFake.setBrightnessThrottlingData(
+        mDeviceConfigFake.setThermalBrightnessThrottlingData(
                 "123,1,critical,0.75;123,1,critical,0.99,id_2");
-        testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.75f);
-        mDeviceConfigFake.setBrightnessThrottlingData(
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.75f);
+        mDeviceConfigFake.setThermalBrightnessThrottlingData(
                 "123,1,critical,0.8,default;123,1,critical,0.99,id_2");
-        testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f);
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f);
     }
 
     @Test public void testInvalidThrottlingStrings() throws Exception {
@@ -327,45 +327,54 @@
                 0.25f);
         List<ThrottlingLevel> levels = new ArrayList<>();
         levels.add(level);
-        final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
+        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
         final BrightnessThrottler throttler = createThrottlerSupported(data);
         verify(mThermalServiceMock).registerThermalEventListenerWithType(
                 mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
         final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
 
         // None of these are valid so shouldn't override the original data
-        mDeviceConfigFake.setBrightnessThrottlingData("321,1,critical,0.4");  // Not the current id
-        testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        mDeviceConfigFake.setBrightnessThrottlingData("123,0,critical,0.4");  // Incorrect number
-        testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        mDeviceConfigFake.setBrightnessThrottlingData("123,2,critical,0.4");  // Incorrect number
-        testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        mDeviceConfigFake.setBrightnessThrottlingData("123,1,invalid,0.4");   // Invalid level
-        testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,none"); // Invalid brightness
-        testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,-3");   // Invalid brightness
-        testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        mDeviceConfigFake.setBrightnessThrottlingData("invalid string");      // Invalid format
-        testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        mDeviceConfigFake.setBrightnessThrottlingData("");                    // Invalid format
-        testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+
+        // Not the current id
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("321,1,critical,0.4");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Incorrect number
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,0,critical,0.4");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Incorrect number
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,2,critical,0.4");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid level
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,invalid,0.4");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid brightness
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,none");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid brightness
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,-3");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid format
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("invalid string");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid format
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
         // Invalid string format
-        mDeviceConfigFake.setBrightnessThrottlingData(
+        mDeviceConfigFake.setThermalBrightnessThrottlingData(
                 "123,default,1,critical,0.75,1,critical,0.99");
-        testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
         // Invalid level string and number string
-        mDeviceConfigFake.setBrightnessThrottlingData(
+        mDeviceConfigFake.setThermalBrightnessThrottlingData(
                 "123,1,1,critical,0.75,id_2,1,critical,0.99");
-        testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
         // Invalid format - (two default ids for same display)
-        mDeviceConfigFake.setBrightnessThrottlingData(
+        mDeviceConfigFake.setThermalBrightnessThrottlingData(
                 "123,1,critical,0.75,default;123,1,critical,0.99");
-        testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
     }
 
-    private void testThrottling(BrightnessThrottler throttler, IThermalEventListener listener,
-            float tooLowCap, float tooHighCap) throws Exception {
+    private void testThermalThrottling(BrightnessThrottler throttler,
+            IThermalEventListener listener, float tooLowCap, float tooHighCap) throws Exception {
         final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
                 tooHighCap);
 
@@ -388,7 +397,7 @@
                 0.25f);
         List<ThrottlingLevel> levels = new ArrayList<>();
         levels.add(level);
-        final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
+        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
 
         // These are identical to the string set below
         final ThrottlingLevel levelSevere = new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE,
@@ -398,7 +407,7 @@
         final ThrottlingLevel levelEmergency = new ThrottlingLevel(
                 PowerManager.THERMAL_STATUS_EMERGENCY, 0.1f);
 
-        mDeviceConfigFake.setBrightnessThrottlingData(
+        mDeviceConfigFake.setThermalBrightnessThrottlingData(
                 "123,3,severe,0.9,critical,0.5,emergency,0.1");
         final BrightnessThrottler throttler = createThrottlerSupported(data);
 
@@ -466,12 +475,13 @@
     private BrightnessThrottler createThrottlerUnsupported() {
         return new BrightnessThrottler(mInjectorMock, mHandler, mHandler,
                 /* throttlingChangeCallback= */ () -> {}, /* uniqueDisplayId= */ null,
-                /* throttlingDataId= */ null, /* throttlingDataMap= */ new HashMap<>(1));
+                /* thermalThrottlingDataId= */ null,
+                /* thermalThrottlingDataMap= */ new HashMap<>(1));
     }
 
-    private BrightnessThrottler createThrottlerSupported(BrightnessThrottlingData data) {
+    private BrightnessThrottler createThrottlerSupported(ThermalBrightnessThrottlingData data) {
         assertNotNull(data);
-        HashMap<String, BrightnessThrottlingData> throttlingDataMap = new HashMap<>(1);
+        HashMap<String, ThermalBrightnessThrottlingData> throttlingDataMap = new HashMap<>(1);
         throttlingDataMap.put("default", data);
         return new BrightnessThrottler(mInjectorMock, mHandler, BackgroundThread.getHandler(),
                 () -> {}, "123", "default", throttlingDataMap);
diff --git a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
index a7d3df9..130e6ad 100644
--- a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -84,11 +84,11 @@
     }
 
     @Test
-    public void testBrightnessThrottlingMapId() {
+    public void testThermalBrightnessThrottlingMapId() {
         Layout configLayout = mDeviceStateToLayoutMap.get(2);
 
-        assertEquals("concurrent1", configLayout.getAt(0).getBrightnessThrottlingMapId());
-        assertEquals("concurrent2", configLayout.getAt(1).getBrightnessThrottlingMapId());
+        assertEquals("concurrent1", configLayout.getAt(0).getThermalBrightnessThrottlingMapId());
+        assertEquals("concurrent2", configLayout.getAt(1).getThermalBrightnessThrottlingMapId());
     }
 
     @Test
@@ -108,7 +108,7 @@
     }
 
     @Test
-    public void testRefreshRateThermalThrottlingMapId() {
+    public void testThermalRefreshRateThrottlingMapId() {
         Layout configLayout = mDeviceStateToLayoutMap.get(4);
 
         assertEquals("test2", configLayout.getAt(0).getRefreshRateThermalThrottlingMapId());
@@ -124,12 +124,14 @@
                 /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
                 mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT, Display.DEFAULT_DISPLAY,
                 /* brightnessThrottlingMapId= */ "brightness1",
-                /* refreshRateZoneId= */ "zone1", /* refreshRateThermalThrottlingMapId= */ "rr1");
+                /* refreshRateZoneId= */ "zone1",
+                /* refreshRateThermalThrottlingMapId= */ "rr1");
         testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L),
                 /* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1",
                 mDisplayIdProducerMock, Layout.Display.POSITION_REAR, Display.DEFAULT_DISPLAY,
                 /* brightnessThrottlingMapId= */ "brightness2",
-                /* refreshRateZoneId= */ "zone2", /* refreshRateThermalThrottlingMapId= */ "rr2");
+                /* refreshRateZoneId= */ "zone2",
+                /* refreshRateThermalThrottlingMapId= */ "rr2");
 
         assertEquals(testLayout, configLayout);
     }
@@ -147,7 +149,8 @@
         layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(id), /* isDefault= */ false,
                 enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
                 Display.DEFAULT_DISPLAY, /* brightnessThrottlingMapId= */ null,
-                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+                /* refreshRateZoneId= */ null,
+                /* refreshRateThermalThrottlingMapId= */ null);
     }
 
     private void setupDeviceStateToLayoutMap() throws IOException {
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index ed07559..5837b21 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -187,72 +187,72 @@
         assertArrayEquals(new int[]{-1, 10, 20, 30, 40},
                 mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux());
 
-        List<DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel>
+        List<DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel>
                 defaultThrottlingLevels = new ArrayList<>();
         defaultThrottlingLevels.add(
-                new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
                 DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.4f
         ));
         defaultThrottlingLevels.add(
-                new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
                 DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.3f
         ));
         defaultThrottlingLevels.add(
-                new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
                 DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.2f
         ));
         defaultThrottlingLevels.add(
-                new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
                 DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.1f
         ));
         defaultThrottlingLevels.add(
-                new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
                 DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.05f
         ));
         defaultThrottlingLevels.add(
-                new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
                 DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.025f
         ));
 
-        DisplayDeviceConfig.BrightnessThrottlingData defaultThrottlingData =
-                new DisplayDeviceConfig.BrightnessThrottlingData(defaultThrottlingLevels);
+        DisplayDeviceConfig.ThermalBrightnessThrottlingData defaultThrottlingData =
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData(defaultThrottlingLevels);
 
-        List<DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel>
+        List<DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel>
                 concurrentThrottlingLevels = new ArrayList<>();
         concurrentThrottlingLevels.add(
-                new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
                 DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.2f
         ));
         concurrentThrottlingLevels.add(
-                new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
                 DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.15f
         ));
         concurrentThrottlingLevels.add(
-                new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
                 DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.1f
         ));
         concurrentThrottlingLevels.add(
-                new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
                 DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.05f
         ));
         concurrentThrottlingLevels.add(
-                new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
                 DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.025f
         ));
         concurrentThrottlingLevels.add(
-                new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
                 DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.0125f
         ));
-        DisplayDeviceConfig.BrightnessThrottlingData concurrentThrottlingData =
-                new DisplayDeviceConfig.BrightnessThrottlingData(concurrentThrottlingLevels);
+        DisplayDeviceConfig.ThermalBrightnessThrottlingData concurrentThrottlingData =
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData(concurrentThrottlingLevels);
 
-        HashMap<String, DisplayDeviceConfig.BrightnessThrottlingData> throttlingDataMap =
+        HashMap<String, DisplayDeviceConfig.ThermalBrightnessThrottlingData> throttlingDataMap =
                 new HashMap<>(2);
         throttlingDataMap.put("default", defaultThrottlingData);
         throttlingDataMap.put("concurrent", concurrentThrottlingData);
 
         assertEquals(throttlingDataMap,
-                mDisplayDeviceConfig.getBrightnessThrottlingDataMapByThrottlingId());
+                mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId());
 
         assertNotNull(mDisplayDeviceConfig.getHostUsiVersion());
         assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2);
@@ -351,16 +351,16 @@
         assertArrayEquals(mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(),
                 HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
 
-        // Todo: Add asserts for BrightnessThrottlingData, DensityMapping,
+        // Todo: Add asserts for ThermalBrightnessThrottlingData, DensityMapping,
         // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
     }
 
     @Test
-    public void testRefreshRateThermalThrottlingFromDisplayConfig() throws IOException {
+    public void testThermalRefreshRateThrottlingFromDisplayConfig() throws IOException {
         setupDisplayDeviceConfigFromDisplayConfigFile();
 
         SparseArray<SurfaceControl.RefreshRateRange> defaultMap =
-                mDisplayDeviceConfig.getRefreshRateThrottlingData(null);
+                mDisplayDeviceConfig.getThermalRefreshRateThrottlingData(null);
         assertNotNull(defaultMap);
         assertEquals(2, defaultMap.size());
         assertEquals(30, defaultMap.get(Temperature.THROTTLING_CRITICAL).min, SMALL_DELTA);
@@ -369,7 +369,7 @@
         assertEquals(30, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).max, SMALL_DELTA);
 
         SparseArray<SurfaceControl.RefreshRateRange> testMap =
-                mDisplayDeviceConfig.getRefreshRateThrottlingData("test");
+                mDisplayDeviceConfig.getThermalRefreshRateThrottlingData("test");
         assertNotNull(testMap);
         assertEquals(1, testMap.size());
         assertEquals(60, testMap.get(Temperature.THROTTLING_EMERGENCY).min, SMALL_DELTA);
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index 3b10db4..e2a66f0 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.display;
 
-import static android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
-import static android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
 import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
 import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
 import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
@@ -29,6 +27,8 @@
 import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.anyFloat;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
@@ -39,14 +39,10 @@
 
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.hardware.display.BrightnessInfo;
 import android.os.Binder;
 import android.os.Handler;
-import android.os.IThermalEventListener;
-import android.os.IThermalService;
 import android.os.Message;
-import android.os.PowerManager;
-import android.os.Temperature;
-import android.os.Temperature.ThrottlingStatus;
 import android.os.test.TestLooper;
 import android.test.mock.MockContentResolver;
 import android.util.MathUtils;
@@ -66,8 +62,6 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -80,7 +74,6 @@
     private static final long TIME_WINDOW_MILLIS = 55 * 1000;
     private static final long TIME_ALLOWED_IN_WINDOW_MILLIS = 12 * 1000;
     private static final long TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS = 5 * 1000;
-    private static final int THERMAL_STATUS_LIMIT = PowerManager.THERMAL_STATUS_SEVERE;
     private static final boolean ALLOW_IN_LOW_POWER_MODE = false;
 
     private static final float DEFAULT_MIN = 0.01f;
@@ -102,17 +95,13 @@
     @Rule
     public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
 
-    @Mock IThermalService mThermalServiceMock;
     @Mock Injector mInjectorMock;
     @Mock HighBrightnessModeController.HdrBrightnessDeviceConfig mHdrBrightnessDeviceConfigMock;
 
-    @Captor ArgumentCaptor<IThermalEventListener> mThermalEventListenerCaptor;
-
     private static final HighBrightnessModeData DEFAULT_HBM_DATA =
             new HighBrightnessModeData(MINIMUM_LUX, TRANSITION_POINT, TIME_WINDOW_MILLIS,
                     TIME_ALLOWED_IN_WINDOW_MILLIS, TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS,
-                    THERMAL_STATUS_LIMIT, ALLOW_IN_LOW_POWER_MODE,
-                    HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT);
+                    ALLOW_IN_LOW_POWER_MODE, HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT);
 
     @Before
     public void setUp() {
@@ -125,8 +114,6 @@
         mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
         when(mContextSpy.getContentResolver()).thenReturn(resolver);
-
-        when(mInjectorMock.getThermalService()).thenReturn(mThermalServiceMock);
     }
 
     /////////////////
@@ -321,34 +308,14 @@
     }
 
     @Test
-    public void testNoHbmInHighThermalState() throws Exception {
+    public void testHbmIsNotTurnedOffInHighThermalState() throws Exception {
         final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
 
-        verify(mThermalServiceMock).registerThermalEventListenerWithType(
-                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
-        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+        // Disabled thermal throttling
+        hbmc.onBrightnessChanged(/*brightness=*/ 1f, /*unthrottledBrightness*/ 1f,
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
 
-        // Set the thermal status too high.
-        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
-
-        // Try to go into HBM mode but fail
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        advanceTime(10);
-
-        assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
-    }
-
-    @Test
-    public void testHbmTurnsOffInHighThermalState() throws Exception {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-
-        verify(mThermalServiceMock).registerThermalEventListenerWithType(
-                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
-        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
-
-        // Set the thermal status tolerable
-        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_LIGHT));
+        assertFalse(hbmc.isThermalThrottlingActive());
 
         // Try to go into HBM mode
         hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
@@ -357,15 +324,19 @@
 
         assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
 
-        // Set the thermal status too high and verify we're off.
-        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
+        // Enable thermal throttling
+        hbmc.onBrightnessChanged(/*brightness=*/ TRANSITION_POINT - 0.01f,
+                /*unthrottledBrightness*/ 1f, BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL);
         advanceTime(10);
-        assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
+        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+        assertTrue(hbmc.isThermalThrottlingActive());
 
-        // Set the thermal status low again and verify we're back on.
-        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_SEVERE));
+        // Disabled thermal throttling
+        hbmc.onBrightnessChanged(/*brightness=*/ 1f, /*unthrottledBrightness*/ 1f,
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
         advanceTime(1);
         assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+        assertFalse(hbmc.isThermalThrottlingActive());
     }
 
     @Test
@@ -578,33 +549,6 @@
             anyInt());
     }
 
-    // Test reporting of thermal throttling when triggered by HighBrightnessModeController's
-    // internal thermal throttling.
-    @Test
-    public void testHbmStats_InternalThermalOff() throws Exception {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-        final int displayStatsId = mDisplayUniqueId.hashCode();
-
-        verify(mThermalServiceMock).registerThermalEventListenerWithType(
-                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
-        final IThermalEventListener thermListener = mThermalEventListenerCaptor.getValue();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-        advanceTime(1);
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
-
-        thermListener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
-        advanceTime(10);
-        assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT));
-    }
-
     // Test reporting of thermal throttling when triggered externally through
     // HighBrightnessModeController.onBrightnessChanged()
     @Test
@@ -617,14 +561,16 @@
         hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
         hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
         // Brightness is unthrottled, HBM brightness granted
-        hbmc.onBrightnessChanged(hbmBrightness, hbmBrightness, BRIGHTNESS_MAX_REASON_NONE);
+        hbmc.onBrightnessChanged(hbmBrightness, hbmBrightness,
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
         advanceTime(1);
         verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
             eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
             eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
 
         // Brightness is thermally throttled, HBM brightness denied (NBM brightness granted)
-        hbmc.onBrightnessChanged(nbmBrightness, hbmBrightness, BRIGHTNESS_MAX_REASON_THERMAL);
+        hbmc.onBrightnessChanged(nbmBrightness, hbmBrightness,
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL);
         advanceTime(1);
         // We expect HBM mode to remain set to sunlight, indicating that HBMC *allows* this mode.
         // However, we expect the HBM state reported by HBMC to be off, since external thermal
@@ -784,11 +730,7 @@
         mTestLooper.dispatchAll();
     }
 
-    private Temperature getSkinTemp(@ThrottlingStatus int status) {
-        return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
-    }
-
     private void hbmcOnBrightnessChanged(HighBrightnessModeController hbmc, float brightness) {
-        hbmc.onBrightnessChanged(brightness, brightness, BRIGHTNESS_MAX_REASON_NONE);
+        hbmc.onBrightnessChanged(brightness, brightness, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 567548e..7536c79 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -649,9 +649,9 @@
         assertEquals(0, mLogicalDisplayMapper.getDisplayLocked(device2)
                 .getLeadDisplayIdLocked());
         assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device1)
-                .getBrightnessThrottlingDataIdLocked());
+                .getThermalBrightnessThrottlingDataIdLocked());
         assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device2)
-                .getBrightnessThrottlingDataIdLocked());
+                .getThermalBrightnessThrottlingDataIdLocked());
 
         mLogicalDisplayMapper.setDeviceStateLocked(1, false);
         advanceTime(1000);
@@ -661,10 +661,10 @@
         assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
         assertEquals(DisplayDeviceConfig.DEFAULT_ID,
                 mLogicalDisplayMapper.getDisplayLocked(device1)
-                        .getBrightnessThrottlingDataIdLocked());
+                        .getThermalBrightnessThrottlingDataIdLocked());
         assertEquals(DisplayDeviceConfig.DEFAULT_ID,
                 mLogicalDisplayMapper.getDisplayLocked(device2)
-                        .getBrightnessThrottlingDataIdLocked());
+                        .getThermalBrightnessThrottlingDataIdLocked());
 
         mLogicalDisplayMapper.setDeviceStateLocked(2, false);
         advanceTime(1000);
@@ -674,10 +674,10 @@
         assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
         assertEquals(DisplayDeviceConfig.DEFAULT_ID,
                 mLogicalDisplayMapper.getDisplayLocked(device1)
-                        .getBrightnessThrottlingDataIdLocked());
+                        .getThermalBrightnessThrottlingDataIdLocked());
         assertEquals(DisplayDeviceConfig.DEFAULT_ID,
                 mLogicalDisplayMapper.getDisplayLocked(device2)
-                        .getBrightnessThrottlingDataIdLocked());
+                        .getThermalBrightnessThrottlingDataIdLocked());
     }
 
     @Test
@@ -863,7 +863,7 @@
         layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer,
                 Layout.Display.POSITION_UNKNOWN, Display.DEFAULT_DISPLAY,
                 /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
-                /* refreshRateThrottlingMapId= */ null);
+                /* refreshRateThermalThrottlingMapId= */ null);
     }
 
     private void advanceTime(long timeMs) {
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index db5a469..6907145 100644
--- a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -2583,7 +2583,7 @@
                     KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps));
         }
 
-        public void setBrightnessThrottlingData(String brightnessThrottlingData) {
+        public void setThermalBrightnessThrottlingData(String brightnessThrottlingData) {
             putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
                     KEY_BRIGHTNESS_THROTTLING_DATA, brightnessThrottlingData);
         }
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index 550204b..4cfbb95 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -105,6 +105,7 @@
         mMockPackageManager = mock(PackageManager.class);
         mMockPackageMonitor = mock(PackageMonitor.class);
 
+        doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt());
         // For unit tests, set the default installer info
         doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)
                 .getInstallSourceInfo(anyString());
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index da9de25..e20f1e7 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -131,6 +131,7 @@
         doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
         doReturn(InstrumentationRegistry.getContext().getContentResolver())
                 .when(mMockContext).getContentResolver();
+        doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt());
 
         mStoragefile = new AtomicFile(new File(
                 Environment.getExternalStorageDirectory(), "systemUpdateUnitTests.xml"));
diff --git a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
index 82bc6f6..06fc017 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
@@ -87,6 +87,7 @@
         Mockito.doReturn(new Intent()).when(mContext).registerReceiverAsUser(
                 any(), any(), any(), any(), any());
         Mockito.doReturn(new Intent()).when(mContext).registerReceiver(any(), any());
+        Mockito.doReturn(new Intent()).when(mContext).registerReceiver(any(), any(), anyInt());
         Mockito.doNothing().when(mContext).unregisterReceiver(any());
     }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 1ba6ad7..eceb589 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -28,6 +28,7 @@
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
 import static android.app.Notification.FLAG_NO_CLEAR;
 import static android.app.Notification.FLAG_ONGOING_EVENT;
+import static android.app.Notification.FLAG_USER_INITIATED_JOB;
 import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
 import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
@@ -227,6 +228,7 @@
 import com.android.server.SystemService;
 import com.android.server.SystemService.TargetUser;
 import com.android.server.UiServiceTestCase;
+import com.android.server.job.JobSchedulerInternal;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
 import com.android.server.notification.NotificationManagerService.NotificationAssistants;
@@ -335,6 +337,8 @@
     @Mock
     ActivityManagerInternal mAmi;
     @Mock
+    JobSchedulerInternal mJsi;
+    @Mock
     private Looper mMainLooper;
     @Mock
     private NotificationManager mMockNm;
@@ -443,6 +447,8 @@
         LocalServices.addService(DeviceIdleInternal.class, deviceIdleInternal);
         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
         LocalServices.addService(ActivityManagerInternal.class, mAmi);
+        LocalServices.removeServiceForTest(JobSchedulerInternal.class);
+        LocalServices.addService(JobSchedulerInternal.class, mJsi);
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
         LocalServices.removeServiceForTest(PermissionPolicyInternal.class);
@@ -1251,7 +1257,7 @@
         waitForIdle();
 
         update = new NotificationChannel("blockedbyuser", "name", IMPORTANCE_NONE);
-        update.setFgServiceShown(true);
+        update.setUserVisibleTaskShown(true);
         mBinderService.updateNotificationChannelForPackage(PKG, mUid, update);
         waitForIdle();
         assertEquals(IMPORTANCE_NONE, mBinderService.getNotificationChannel(
@@ -8609,7 +8615,7 @@
         assertNotNull(n.publicVersion.bigContentView);
         assertNotNull(n.publicVersion.headsUpContentView);
 
-        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
 
         assertNull(n.contentView);
         assertNull(n.bigContentView);
@@ -10439,7 +10445,7 @@
                 .setFullScreenIntent(mock(PendingIntent.class), true)
                 .build();
 
-        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
 
         final int stickyFlag = n.flags & Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
 
@@ -10524,7 +10530,7 @@
                 .setFlag(FLAG_CAN_COLORIZE, true)
                 .build();
 
-        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
 
         assertFalse(n.isForegroundService());
         assertFalse(n.hasColorizedPermission());
@@ -10552,7 +10558,7 @@
                 .build();
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
 
         // Then: the notification's flag FLAG_NO_DISMISS should not be set
         assertSame(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10571,7 +10577,7 @@
                 .build();
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
 
         // Then: the notification's flag FLAG_NO_DISMISS should be set
         assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10595,7 +10601,7 @@
                 .build();
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
 
         // Then: the notification's flag FLAG_NO_DISMISS should be set
         assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10612,7 +10618,7 @@
                 .build();
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
 
         // Then: the notification's flag FLAG_NO_DISMISS should not be set
         assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10630,7 +10636,7 @@
         n.flags |= Notification.FLAG_NO_DISMISS;
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
 
         // Then: the notification's flag FLAG_NO_DISMISS should be cleared
         assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10648,7 +10654,7 @@
                 .build();
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
 
         // Then: the notification's flag FLAG_NO_DISMISS should not be set
         assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10669,7 +10675,7 @@
         n.flags |= Notification.FLAG_NO_DISMISS;
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
 
         // Then: the notification's flag FLAG_NO_DISMISS should be cleared
         assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10686,7 +10692,7 @@
                 .build();
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
 
         // Then: the notification's flag FLAG_NO_DISMISS should not be set
         assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10705,7 +10711,7 @@
                 .build();
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
 
         // Then: the notification's flag FLAG_NO_DISMISS should be set
         assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10733,7 +10739,7 @@
                 .build();
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
 
         // Then: the notification's flag FLAG_NO_DISMISS should be cleared
         assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
@@ -10754,12 +10760,589 @@
                 .build();
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
 
         // Then: the notification's flag FLAG_NO_DISMISS should not be set
         assertSame(0, n.flags & Notification.FLAG_NO_DISMISS);
     }
 
+    @Test
+    public void testCancelAllNotifications_IgnoreUserInitiatedJob() throws Exception {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
+        sbn.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelAllNotifications_IgnoreUserInitiatedJob",
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(sbn.getPackageName());
+        assertEquals(1, notifs.length);
+        assertEquals(1, mService.getNotificationRecordCount());
+    }
+
+    @Test
+    public void testCancelAllNotifications_UijFlag_NoUij_Allowed() throws Exception {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(false);
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
+        sbn.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelAllNotifications_UijFlag_NoUij_Allowed",
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(sbn.getPackageName());
+        assertEquals(0, notifs.length);
+    }
+
+    @Test
+    public void testCancelAllNotificationsOtherPackage_IgnoresUijNotification() throws Exception {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
+        sbn.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelAllNotifications_IgnoreOtherPackages",
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        mBinderService.cancelAllNotifications("other_pkg_name", sbn.getUserId());
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(sbn.getPackageName());
+        assertEquals(1, notifs.length);
+        assertEquals(1, mService.getNotificationRecordCount());
+    }
+
+    @Test
+    public void testRemoveUserInitiatedJobFlag_ImmediatelyAfterEnqueue() throws Exception {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .build();
+        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, null, mUid, 0,
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
+        sbn.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        mInternalService.removeUserInitiatedJobFlagFromNotification(PKG, sbn.getId(),
+                sbn.getUserId());
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(sbn.getPackageName());
+        assertFalse(notifs[0].getNotification().isUserInitiatedJob());
+    }
+
+    @Test
+    public void testCancelAfterSecondEnqueueDoesNotSpecifyUserInitiatedJobFlag() throws Exception {
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
+        sbn.getNotification().flags = Notification.FLAG_ONGOING_EVENT | FLAG_USER_INITIATED_JOB;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        sbn.getNotification().flags = Notification.FLAG_ONGOING_EVENT;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        mBinderService.cancelNotificationWithTag(PKG, PKG, sbn.getTag(), sbn.getId(),
+                sbn.getUserId());
+        waitForIdle();
+        assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
+        assertEquals(0, mService.getNotificationRecordCount());
+    }
+
+    @Test
+    public void testCancelNotificationWithTag_fromApp_cannotCancelUijChild() throws Exception {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        mService.isSystemUid = false;
+        final NotificationRecord parent = generateNotificationRecord(
+                mTestNotificationChannel, 1, "group", true);
+        final NotificationRecord child = generateNotificationRecord(
+                mTestNotificationChannel, 2, "group", false);
+        final NotificationRecord child2 = generateNotificationRecord(
+                mTestNotificationChannel, 3, "group", false);
+        child2.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+        mService.addNotification(parent);
+        mService.addNotification(child);
+        mService.addNotification(child2);
+        mService.getBinderService().cancelNotificationWithTag(
+                parent.getSbn().getPackageName(), parent.getSbn().getPackageName(),
+                parent.getSbn().getTag(), parent.getSbn().getId(), parent.getSbn().getUserId());
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
+        assertEquals(1, notifs.length);
+    }
+
+    @Test
+    public void testCancelNotificationWithTag_fromApp_cannotCancelUijParent() throws Exception {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        mService.isSystemUid = false;
+        final NotificationRecord parent = generateNotificationRecord(
+                mTestNotificationChannel, 1, "group", true);
+        parent.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+        final NotificationRecord child = generateNotificationRecord(
+                mTestNotificationChannel, 2, "group", false);
+        final NotificationRecord child2 = generateNotificationRecord(
+                mTestNotificationChannel, 3, "group", false);
+        mService.addNotification(parent);
+        mService.addNotification(child);
+        mService.addNotification(child2);
+        mService.getBinderService().cancelNotificationWithTag(
+                parent.getSbn().getPackageName(), parent.getSbn().getPackageName(),
+                parent.getSbn().getTag(), parent.getSbn().getId(), parent.getSbn().getUserId());
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
+        assertEquals(3, notifs.length);
+    }
+
+    @Test
+    public void testCancelAllNotificationsFromApp_cannotCancelUijChild() throws Exception {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        mService.isSystemUid = false;
+        final NotificationRecord parent = generateNotificationRecord(
+                mTestNotificationChannel, 1, "group", true);
+        final NotificationRecord child = generateNotificationRecord(
+                mTestNotificationChannel, 2, "group", false);
+        final NotificationRecord child2 = generateNotificationRecord(
+                mTestNotificationChannel, 3, "group", false);
+        child2.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+        final NotificationRecord newGroup = generateNotificationRecord(
+                mTestNotificationChannel, 4, "group2", false);
+        mService.addNotification(parent);
+        mService.addNotification(child);
+        mService.addNotification(child2);
+        mService.addNotification(newGroup);
+        mService.getBinderService().cancelAllNotifications(
+                parent.getSbn().getPackageName(), parent.getSbn().getUserId());
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
+        assertEquals(1, notifs.length);
+    }
+
+    @Test
+    public void testCancelAllNotifications_fromApp_cannotCancelUijParent() throws Exception {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        mService.isSystemUid = false;
+        final NotificationRecord parent = generateNotificationRecord(
+                mTestNotificationChannel, 1, "group", true);
+        parent.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+        final NotificationRecord child = generateNotificationRecord(
+                mTestNotificationChannel, 2, "group", false);
+        final NotificationRecord child2 = generateNotificationRecord(
+                mTestNotificationChannel, 3, "group", false);
+        final NotificationRecord newGroup = generateNotificationRecord(
+                mTestNotificationChannel, 4, "group2", false);
+        mService.addNotification(parent);
+        mService.addNotification(child);
+        mService.addNotification(child2);
+        mService.addNotification(newGroup);
+        mService.getBinderService().cancelAllNotifications(
+                parent.getSbn().getPackageName(), parent.getSbn().getUserId());
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
+        assertEquals(1, notifs.length);
+    }
+
+    @Test
+    public void testCancelNotificationsFromListener_clearAll_GroupWithUijParent() throws Exception {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        final NotificationRecord parent = generateNotificationRecord(
+                mTestNotificationChannel, 1, "group", true);
+        parent.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+        final NotificationRecord child = generateNotificationRecord(
+                mTestNotificationChannel, 2, "group", false);
+        final NotificationRecord child2 = generateNotificationRecord(
+                mTestNotificationChannel, 3, "group", false);
+        final NotificationRecord newGroup = generateNotificationRecord(
+                mTestNotificationChannel, 4, "group2", false);
+        mService.addNotification(parent);
+        mService.addNotification(child);
+        mService.addNotification(child2);
+        mService.addNotification(newGroup);
+        mService.getBinderService().cancelNotificationsFromListener(null, null);
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
+        assertEquals(0, notifs.length);
+    }
+
+    @Test
+    public void testCancelNotificationsFromListener_clearAll_GroupWithUijChild() throws Exception {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        final NotificationRecord parent = generateNotificationRecord(
+                mTestNotificationChannel, 1, "group", true);
+        final NotificationRecord child = generateNotificationRecord(
+                mTestNotificationChannel, 2, "group", false);
+        final NotificationRecord child2 = generateNotificationRecord(
+                mTestNotificationChannel, 3, "group", false);
+        child2.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+        final NotificationRecord newGroup = generateNotificationRecord(
+                mTestNotificationChannel, 4, "group2", false);
+        mService.addNotification(parent);
+        mService.addNotification(child);
+        mService.addNotification(child2);
+        mService.addNotification(newGroup);
+        mService.getBinderService().cancelNotificationsFromListener(null, null);
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
+        assertEquals(0, notifs.length);
+    }
+
+    @Test
+    public void testCancelNotificationsFromListener_clearAll_Uij() throws Exception {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        final NotificationRecord child2 = generateNotificationRecord(
+                mTestNotificationChannel, 3, null, false);
+        child2.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+        mService.addNotification(child2);
+        mService.getBinderService().cancelNotificationsFromListener(null, null);
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(child2.getSbn().getPackageName());
+        assertEquals(0, notifs.length);
+    }
+
+    @Test
+    public void testCancelNotificationsFromListener_byKey_GroupWithUijParent() throws Exception {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        final NotificationRecord parent = generateNotificationRecord(
+                mTestNotificationChannel, 1, "group", true);
+        parent.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+        final NotificationRecord child = generateNotificationRecord(
+                mTestNotificationChannel, 2, "group", false);
+        final NotificationRecord child2 = generateNotificationRecord(
+                mTestNotificationChannel, 3, "group", false);
+        final NotificationRecord newGroup = generateNotificationRecord(
+                mTestNotificationChannel, 4, "group2", false);
+        mService.addNotification(parent);
+        mService.addNotification(child);
+        mService.addNotification(child2);
+        mService.addNotification(newGroup);
+        String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(),
+                child2.getSbn().getKey(), newGroup.getSbn().getKey()};
+        mService.getBinderService().cancelNotificationsFromListener(null, keys);
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
+        assertEquals(0, notifs.length);
+    }
+
+    @Test
+    public void testCancelNotificationsFromListener_byKey_GroupWithUijChild() throws Exception {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        final NotificationRecord parent = generateNotificationRecord(
+                mTestNotificationChannel, 1, "group", true);
+        final NotificationRecord child = generateNotificationRecord(
+                mTestNotificationChannel, 2, "group", false);
+        final NotificationRecord child2 = generateNotificationRecord(
+                mTestNotificationChannel, 3, "group", false);
+        child2.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+        final NotificationRecord newGroup = generateNotificationRecord(
+                mTestNotificationChannel, 4, "group2", false);
+        mService.addNotification(parent);
+        mService.addNotification(child);
+        mService.addNotification(child2);
+        mService.addNotification(newGroup);
+        String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(),
+                child2.getSbn().getKey(), newGroup.getSbn().getKey()};
+        mService.getBinderService().cancelNotificationsFromListener(null, keys);
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
+        assertEquals(0, notifs.length);
+    }
+
+    @Test
+    public void testCancelNotificationsFromListener_byKey_Uij() throws Exception {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        final NotificationRecord child = generateNotificationRecord(
+                mTestNotificationChannel, 3, null, false);
+        child.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+        mService.addNotification(child);
+        String[] keys = {child.getSbn().getKey()};
+        mService.getBinderService().cancelNotificationsFromListener(null, keys);
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(child.getSbn().getPackageName());
+        assertEquals(0, notifs.length);
+    }
+
+    @Test
+    public void testUserInitiatedCancelAllWithGroup_UserInitiatedFlag() throws Exception {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        final NotificationRecord parent = generateNotificationRecord(
+                mTestNotificationChannel, 1, "group", true);
+        final NotificationRecord child = generateNotificationRecord(
+                mTestNotificationChannel, 2, "group", false);
+        final NotificationRecord child2 = generateNotificationRecord(
+                mTestNotificationChannel, 3, "group", false);
+        child2.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+        final NotificationRecord newGroup = generateNotificationRecord(
+                mTestNotificationChannel, 4, "group2", false);
+        mService.addNotification(parent);
+        mService.addNotification(child);
+        mService.addNotification(child2);
+        mService.addNotification(newGroup);
+        mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(), parent.getUserId());
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
+        assertEquals(0, notifs.length);
+    }
+
+    @Test
+    public void testDeleteChannelGroupChecksForUijs() throws Exception {
+        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+                .thenReturn(singletonList(mock(AssociationInfo.class)));
+        CountDownLatch latch = new CountDownLatch(2);
+        mService.createNotificationChannelGroup(PKG, mUid,
+                new NotificationChannelGroup("group", "group"), true, false);
+        new Thread(() -> {
+            NotificationChannel notificationChannel = new NotificationChannel("id", "id",
+                    NotificationManager.IMPORTANCE_HIGH);
+            notificationChannel.setGroup("group");
+            ParceledListSlice<NotificationChannel> pls =
+                    new ParceledListSlice(ImmutableList.of(notificationChannel));
+            try {
+                mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+            latch.countDown();
+        }).start();
+        new Thread(() -> {
+            try {
+                synchronized (this) {
+                    wait(5000);
+                }
+                mService.createNotificationChannelGroup(PKG, mUid,
+                        new NotificationChannelGroup("new", "new group"), true, false);
+                NotificationChannel notificationChannel =
+                        new NotificationChannel("id", "id", NotificationManager.IMPORTANCE_HIGH);
+                notificationChannel.setGroup("new");
+                ParceledListSlice<NotificationChannel> pls =
+                        new ParceledListSlice(ImmutableList.of(notificationChannel));
+                try {
+                    mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
+                    mBinderService.deleteNotificationChannelGroup(PKG, "group");
+                } catch (RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            latch.countDown();
+        }).start();
+
+        latch.await();
+        verify(mJsi).isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
+                anyString(), anyInt(), anyString());
+    }
+
+    @Test
+    public void testRemoveUserInitiatedJobFlagFromNotification_enqueued() {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        Notification n = new Notification.Builder(mContext, "").build();
+        n.flags |= FLAG_USER_INITIATED_JOB;
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 9, null, mUid, 0,
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mService.addEnqueuedNotification(r);
+
+        mInternalService.removeUserInitiatedJobFlagFromNotification(
+                PKG, r.getSbn().getId(), r.getSbn().getUserId());
+
+        waitForIdle();
+
+        verify(mListeners, timeout(200).times(0)).notifyPostedLocked(any(), any());
+    }
+
+    @Test
+    public void testRemoveUserInitiatedJobFlagFromNotification_posted() {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        Notification n = new Notification.Builder(mContext, "").build();
+        n.flags |= FLAG_USER_INITIATED_JOB;
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 9, null, mUid, 0,
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mService.addNotification(r);
+
+        mInternalService.removeUserInitiatedJobFlagFromNotification(
+                PKG, r.getSbn().getId(), r.getSbn().getUserId());
+
+        waitForIdle();
+
+        ArgumentCaptor<NotificationRecord> captor =
+                ArgumentCaptor.forClass(NotificationRecord.class);
+        verify(mListeners, times(1)).notifyPostedLocked(captor.capture(), any());
+
+        assertEquals(0, captor.getValue().getNotification().flags);
+    }
+
+    @Test
+    public void testCannotRemoveUserInitiatedJobFlagWhenOverLimit_enqueued() {
+        for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) {
+            Notification n = new Notification.Builder(mContext, "").build();
+            StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, i, null, mUid, 0,
+                    n, UserHandle.getUserHandleForUid(mUid), null, 0);
+            NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+            mService.addEnqueuedNotification(r);
+        }
+        Notification n = new Notification.Builder(mContext, "").build();
+        n.flags |= FLAG_USER_INITIATED_JOB;
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG,
+                NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS, null, mUid, 0,
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mService.addEnqueuedNotification(r);
+
+        mInternalService.removeUserInitiatedJobFlagFromNotification(
+                PKG, r.getSbn().getId(), r.getSbn().getUserId());
+
+        waitForIdle();
+
+        assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS,
+                mService.getNotificationRecordCount());
+    }
+
+    @Test
+    public void testCannotRemoveUserInitiatedJobFlagWhenOverLimit_posted() {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) {
+            Notification n = new Notification.Builder(mContext, "").build();
+            StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, i, null, mUid, 0,
+                    n, UserHandle.getUserHandleForUid(mUid), null, 0);
+            NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+            mService.addNotification(r);
+        }
+        Notification n = new Notification.Builder(mContext, "").build();
+        n.flags |= FLAG_USER_INITIATED_JOB;
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG,
+                NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS, null, mUid, 0,
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mService.addNotification(r);
+
+        mInternalService.removeUserInitiatedJobFlagFromNotification(
+                PKG, r.getSbn().getId(), r.getSbn().getUserId());
+
+        waitForIdle();
+
+        assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS,
+                mService.getNotificationRecordCount());
+    }
+
+    @Test
+    public void testCanPostUijWhenOverLimit() throws RemoteException {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) {
+            StatusBarNotification sbn = generateNotificationRecord(mTestNotificationChannel,
+                    i, null, false).getSbn();
+            mBinderService.enqueueNotificationWithTag(PKG, PKG, "testCanPostUijWhenOverLimit",
+                    sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        }
+
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
+        sbn.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCanPostUijWhenOverLimit - uij over limit!",
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+
+        waitForIdle();
+
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(sbn.getPackageName());
+        assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS + 1, notifs.length);
+        assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS + 1,
+                mService.getNotificationRecordCount());
+    }
+
+    @Test
+    public void testCannotPostNonUijWhenOverLimit() throws RemoteException {
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(true);
+        for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) {
+            StatusBarNotification sbn = generateNotificationRecord(mTestNotificationChannel,
+                    i, null, false).getSbn();
+            mBinderService.enqueueNotificationWithTag(PKG, PKG, "testCannotPostNonUijWhenOverLimit",
+                    sbn.getId(), sbn.getNotification(), sbn.getUserId());
+            waitForIdle();
+        }
+
+        final StatusBarNotification sbn = generateNotificationRecord(mTestNotificationChannel,
+                100, null, false).getSbn();
+        sbn.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCannotPostNonUijWhenOverLimit - uij over limit!",
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+
+        final StatusBarNotification sbn2 = generateNotificationRecord(mTestNotificationChannel,
+                101, null, false).getSbn();
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCannotPostNonUijWhenOverLimit - non uij over limit!",
+                sbn2.getId(), sbn2.getNotification(), sbn2.getUserId());
+
+        when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
+                .thenReturn(false);
+        final StatusBarNotification sbn3 = generateNotificationRecord(mTestNotificationChannel,
+                101, null, false).getSbn();
+        sbn3.getNotification().flags |= FLAG_USER_INITIATED_JOB;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCannotPostNonUijWhenOverLimit - fake uij over limit!",
+                sbn3.getId(), sbn3.getNotification(), sbn3.getUserId());
+
+        waitForIdle();
+
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(sbn.getPackageName());
+        assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS + 1, notifs.length);
+        assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS + 1,
+                mService.getNotificationRecordCount());
+    }
+
+    @Test
+    public void fixNotification_withUijFlag_butIsNotUij() throws Exception {
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenReturn(applicationInfo);
+
+        Notification n = new Notification.Builder(mContext, "test")
+                .setFlag(FLAG_USER_INITIATED_JOB, true)
+                .build();
+
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
+        assertFalse(n.isUserInitiatedJob());
+    }
+
     private void setDpmAppOppsExemptFromDismissal(boolean isOn) {
         DeviceConfig.setProperty(
                 DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index b1a9f08..34bb664 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -96,8 +96,6 @@
     private TestableNotificationManagerService mService;
     private NotificationManagerService.RoleObserver mRoleObserver;
 
-    private TestableContext mContext = spy(getContext());
-
     @Mock
     private PreferencesHelper mPreferencesHelper;
     @Mock
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
new file mode 100644
index 0000000..bcd807a
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.provider.Settings;
+import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeDiff;
+import android.service.notification.ZenPolicy;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.ArrayMap;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ZenModeDiffTest extends UiServiceTestCase {
+    // version is not included in the diff; manual & automatic rules have special handling
+    public static final Set<String> ZEN_MODE_CONFIG_EXEMPT_FIELDS =
+            Set.of("version", "manualRule", "automaticRules");
+
+    @Test
+    public void testRuleDiff_addRemoveSame() {
+        // Test add, remove, and both sides same
+        ZenModeConfig.ZenRule r = makeRule();
+
+        // Both sides same rule
+        ZenModeDiff.RuleDiff dSame = new ZenModeDiff.RuleDiff(r, r);
+        assertFalse(dSame.hasDiff());
+
+        // from existent rule to null: expect deleted
+        ZenModeDiff.RuleDiff deleted = new ZenModeDiff.RuleDiff(r, null);
+        assertTrue(deleted.hasDiff());
+        assertTrue(deleted.wasRemoved());
+
+        // from null to new rule: expect added
+        ZenModeDiff.RuleDiff added = new ZenModeDiff.RuleDiff(null, r);
+        assertTrue(added.hasDiff());
+        assertTrue(added.wasAdded());
+    }
+
+    @Test
+    public void testRuleDiff_fieldDiffs() throws Exception {
+        // Start these the same
+        ZenModeConfig.ZenRule r1 = makeRule();
+        ZenModeConfig.ZenRule r2 = makeRule();
+
+        // maps mapping field name -> expected output value as we set diffs
+        ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+        ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+        List<Field> fieldsForDiff = getFieldsForDiffCheck(
+                ZenModeConfig.ZenRule.class, Set.of()); // actually no exempt fields for ZenRule
+        generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo);
+
+        ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
+        assertTrue(d.hasDiff());
+
+        // Now diff them and check that each of the fields has a diff
+        for (Field f : fieldsForDiff) {
+            String name = f.getName();
+            assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+            assertTrue(d.getDiffForField(name).hasDiff());
+            assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+            assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+            assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+            assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+        }
+    }
+
+    @Test
+    public void testConfigDiff_addRemoveSame() {
+        // Default config, will test add, remove, and no change
+        ZenModeConfig c = new ZenModeConfig();
+
+        ZenModeDiff.ConfigDiff dSame = new ZenModeDiff.ConfigDiff(c, c);
+        assertFalse(dSame.hasDiff());
+
+        ZenModeDiff.ConfigDiff added = new ZenModeDiff.ConfigDiff(null, c);
+        assertTrue(added.hasDiff());
+        assertTrue(added.wasAdded());
+
+        ZenModeDiff.ConfigDiff removed = new ZenModeDiff.ConfigDiff(c, null);
+        assertTrue(removed.hasDiff());
+        assertTrue(removed.wasRemoved());
+    }
+
+    @Test
+    public void testConfigDiff_fieldDiffs() throws Exception {
+        // these two start the same
+        ZenModeConfig c1 = new ZenModeConfig();
+        ZenModeConfig c2 = new ZenModeConfig();
+
+        // maps mapping field name -> expected output value as we set diffs
+        ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+        ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+        List<Field> fieldsForDiff = getFieldsForDiffCheck(
+                ZenModeConfig.class, ZEN_MODE_CONFIG_EXEMPT_FIELDS);
+        generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo);
+
+        ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
+        assertTrue(d.hasDiff());
+
+        // Now diff them and check that each of the fields has a diff
+        for (Field f : fieldsForDiff) {
+            String name = f.getName();
+            assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+            assertTrue(d.getDiffForField(name).hasDiff());
+            assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+            assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+            assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+            assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+        }
+    }
+
+    @Test
+    public void testConfigDiff_specialSenders() {
+        // these two start the same
+        ZenModeConfig c1 = new ZenModeConfig();
+        ZenModeConfig c2 = new ZenModeConfig();
+
+        // set c1 and c2 to have some different senders
+        c1.allowMessagesFrom = ZenModeConfig.SOURCE_STAR;
+        c2.allowMessagesFrom = ZenModeConfig.SOURCE_CONTACT;
+        c1.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+        c2.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_NONE;
+
+        ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
+        assertTrue(d.hasDiff());
+
+        // Diff in top-level fields
+        assertTrue(d.getDiffForField("allowMessagesFrom").hasDiff());
+        assertTrue(d.getDiffForField("allowConversationsFrom").hasDiff());
+
+        // Bonus testing of stringification of people senders and conversation senders
+        assertTrue(d.toString().contains("allowMessagesFrom:stars->contacts"));
+        assertTrue(d.toString().contains("allowConversationsFrom:important->none"));
+    }
+
+    @Test
+    public void testConfigDiff_hasRuleDiffs() {
+        // two default configs
+        ZenModeConfig c1 = new ZenModeConfig();
+        ZenModeConfig c2 = new ZenModeConfig();
+
+        // two initially-identical rules
+        ZenModeConfig.ZenRule r1 = makeRule();
+        ZenModeConfig.ZenRule r2 = makeRule();
+
+        // one that will become a manual rule
+        ZenModeConfig.ZenRule m = makeRule();
+
+        // Add r1 to c1, but not r2 to c2 yet -- expect a rule to be deleted
+        c1.automaticRules.put(r1.id, r1);
+        ZenModeDiff.ConfigDiff deleteRule = new ZenModeDiff.ConfigDiff(c1, c2);
+        assertTrue(deleteRule.hasDiff());
+        assertNotNull(deleteRule.getAllAutomaticRuleDiffs());
+        assertTrue(deleteRule.getAllAutomaticRuleDiffs().containsKey("ruleId"));
+        assertTrue(deleteRule.getAllAutomaticRuleDiffs().get("ruleId").wasRemoved());
+
+        // Change r2 a little, add r2 to c2 as an automatic rule and m as a manual rule
+        r2.component = null;
+        r2.pkg = "different";
+        c2.manualRule = m;
+        c2.automaticRules.put(r2.id, r2);
+
+        // Expect diffs in both manual rule (added) and automatic rule (changed)
+        ZenModeDiff.ConfigDiff changed = new ZenModeDiff.ConfigDiff(c1, c2);
+        assertTrue(changed.hasDiff());
+        assertTrue(changed.getManualRuleDiff().hasDiff());
+
+        ArrayMap<String, ZenModeDiff.RuleDiff> automaticDiffs = changed.getAllAutomaticRuleDiffs();
+        assertNotNull(automaticDiffs);
+        assertTrue(automaticDiffs.containsKey("ruleId"));
+        assertNotNull(automaticDiffs.get("ruleId").getDiffForField("component"));
+        assertNull(automaticDiffs.get("ruleId").getDiffForField("component").to());
+        assertNotNull(automaticDiffs.get("ruleId").getDiffForField("pkg"));
+        assertEquals("different", automaticDiffs.get("ruleId").getDiffForField("pkg").to());
+    }
+
+    // Helper methods for working with configs, policies, rules
+    // Just makes a zen rule with fields filled in
+    private ZenModeConfig.ZenRule makeRule() {
+        ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+        rule.configurationActivity = new ComponentName("a", "a");
+        rule.component = new ComponentName("b", "b");
+        rule.conditionId = new Uri.Builder().scheme("hello").build();
+        rule.condition = new Condition(rule.conditionId, "", Condition.STATE_TRUE);
+        rule.enabled = true;
+        rule.creationTime = 123;
+        rule.id = "ruleId";
+        rule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        rule.modified = false;
+        rule.name = "name";
+        rule.snoozing = true;
+        rule.pkg = "a";
+        return rule;
+    }
+
+    // Get the fields on which we would want to check a diff. The requirements are: not final or/
+    // static (as these should/can never change), and not in a specific list that's exempted.
+    private List<Field> getFieldsForDiffCheck(Class c, Set<String> exemptNames)
+            throws SecurityException {
+        Field[] fields = c.getDeclaredFields();
+        ArrayList<Field> out = new ArrayList<>();
+
+        for (Field field : fields) {
+            // Check for exempt reasons
+            int m = field.getModifiers();
+            if (Modifier.isFinal(m)
+                    || Modifier.isStatic(m)
+                    || exemptNames.contains(field.getName())) {
+                continue;
+            }
+            out.add(field);
+        }
+        return out;
+    }
+
+    // Generate a set of generic diffs for the specified two objects and the fields to generate
+    // diffs for, and store the results in the provided expectation maps to be able to check the
+    // output later.
+    private void generateFieldDiffs(Object a, Object b, List<Field> fields,
+            ArrayMap<String, Object> expectedA, ArrayMap<String, Object> expectedB)
+            throws Exception {
+        // different classes passed in means bad input
+        assertEquals(a.getClass(), b.getClass());
+
+        // Loop through fields for which we want to check diffs, set a diff and keep track of
+        // what we set.
+        for (Field f : fields) {
+            f.setAccessible(true);
+            // Just double-check also that the fields actually are for the class declared
+            assertEquals(f.getDeclaringClass(), a.getClass());
+            Class t = f.getType();
+            // handle the full set of primitive types first
+            if (boolean.class.equals(t)) {
+                f.setBoolean(a, true);
+                expectedA.put(f.getName(), true);
+                f.setBoolean(b, false);
+                expectedB.put(f.getName(), false);
+            } else if (int.class.equals(t)) {
+                // these are not actually valid going to be valid for arbitrary int enum fields, but
+                // we just put something in there regardless.
+                f.setInt(a, 2);
+                expectedA.put(f.getName(), 2);
+                f.setInt(b, 1);
+                expectedB.put(f.getName(), 1);
+            } else if (long.class.equals(t)) {
+                f.setLong(a, 200L);
+                expectedA.put(f.getName(), 200L);
+                f.setLong(b, 100L);
+                expectedB.put(f.getName(), 100L);
+            } else if (t.isPrimitive()) {
+                // This method doesn't yet handle other primitive types. If the relevant diff
+                // classes gain new fields of these types, please add another clause here.
+                fail("primitive type not handled by generateFieldDiffs: " + t.getName());
+            } else if (String.class.equals(t)) {
+                f.set(a, "string1");
+                expectedA.put(f.getName(), "string1");
+                f.set(b, "string2");
+                expectedB.put(f.getName(), "string2");
+            } else {
+                // catch-all for other types: have the field be "added"
+                f.set(a, null);
+                expectedA.put(f.getName(), null);
+                try {
+                    f.set(b, t.getDeclaredConstructor().newInstance());
+                    expectedB.put(f.getName(), t.getDeclaredConstructor().newInstance());
+                } catch (Exception e) {
+                    // No default constructor, or blithely attempting to construct something doesn't
+                    // work for some reason. If the default value isn't null, then keep it.
+                    if (f.get(b) != null) {
+                        expectedB.put(f.getName(), f.get(b));
+                    } else {
+                        // If we can't even rely on that, fail. Have the test-writer special case
+                        // something, as this is not able to be genericized.
+                        fail("could not generically construct value for field: " + f.getName());
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 6f9798e..b2a5401 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -69,8 +69,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.AutomaticZenRule;
@@ -78,7 +76,6 @@
 import android.app.NotificationManager.Policy;
 import android.content.ComponentName;
 import android.content.ContentResolver;
-import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -90,7 +87,6 @@
 import android.media.AudioSystem;
 import android.media.VolumePolicy;
 import android.net.Uri;
-import android.os.Binder;
 import android.os.Process;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -98,6 +94,7 @@
 import android.service.notification.Condition;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.service.notification.ZenModeDiff;
 import android.service.notification.ZenPolicy;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -877,7 +874,8 @@
         mZenModeHelperSpy.readXml(parser, false, UserHandle.USER_ALL);
 
         assertEquals("Config mismatch: current vs expected: "
-                + mZenModeHelperSpy.mConfig.diff(expected), expected, mZenModeHelperSpy.mConfig);
+                + new ZenModeDiff.ConfigDiff(mZenModeHelperSpy.mConfig, expected), expected,
+                mZenModeHelperSpy.mConfig);
     }
 
     @Test
@@ -1046,7 +1044,8 @@
 
         ZenModeConfig actual = mZenModeHelperSpy.mConfigs.get(10);
         assertEquals(
-                "Config mismatch: current vs expected: " + actual.diff(config10), config10, actual);
+                "Config mismatch: current vs expected: "
+                        + new ZenModeDiff.ConfigDiff(actual, config10), config10, actual);
         assertNotEquals("Expected config mismatch", config11, mZenModeHelperSpy.mConfigs.get(11));
     }
 
@@ -1062,7 +1061,8 @@
         mZenModeHelperSpy.readXml(parser, true, UserHandle.USER_SYSTEM);
 
         assertEquals("Config mismatch: current vs original: "
-                + mZenModeHelperSpy.mConfig.diff(original), original, mZenModeHelperSpy.mConfig);
+                + new ZenModeDiff.ConfigDiff(mZenModeHelperSpy.mConfig, original),
+                original, mZenModeHelperSpy.mConfig);
         assertEquals(original.hashCode(), mZenModeHelperSpy.mConfig.hashCode());
     }
 
@@ -1083,8 +1083,9 @@
 
         ZenModeConfig actual = mZenModeHelperSpy.mConfigs.get(10);
         expected.user = 10;
-        assertEquals(
-                "Config mismatch: current vs original: " + actual.diff(expected), expected, actual);
+        assertEquals("Config mismatch: current vs original: "
+                        + new ZenModeDiff.ConfigDiff(actual, expected),
+                expected, actual);
         assertEquals(expected.hashCode(), actual.hashCode());
         expected.user = 0;
         assertNotEquals(expected, mZenModeHelperSpy.mConfig);
diff --git a/services/tests/voiceinteractiontests/Android.bp b/services/tests/voiceinteractiontests/Android.bp
index 986fb71..e704ebf 100644
--- a/services/tests/voiceinteractiontests/Android.bp
+++ b/services/tests/voiceinteractiontests/Android.bp
@@ -40,6 +40,7 @@
         "platform-test-annotations",
         "services.core",
         "services.voiceinteraction",
+        "services.soundtrigger",
         "servicestests-core-utils",
         "servicestests-utils-mockito-extended",
         "truth-prebuilt",
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index b8a21ec..8f2b470 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -83,6 +83,7 @@
 import static com.android.server.wm.ActivityRecord.FINISH_RESULT_CANCELLED;
 import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REMOVED;
 import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REQUESTED;
+import static com.android.server.wm.ActivityRecord.LAUNCH_SOURCE_TYPE_HOME;
 import static com.android.server.wm.ActivityRecord.State.DESTROYED;
 import static com.android.server.wm.ActivityRecord.State.DESTROYING;
 import static com.android.server.wm.ActivityRecord.State.FINISHING;
@@ -589,12 +590,18 @@
                 throw new IllegalStateException("Orientation in new config should be either"
                         + "landscape or portrait.");
         }
+
+        final DisplayRotation displayRotation = activity.mDisplayContent.getDisplayRotation();
+        spyOn(displayRotation);
+
         activity.setRequestedOrientation(requestedOrientation);
 
         final ActivityConfigurationChangeItem expected =
                 ActivityConfigurationChangeItem.obtain(newConfig);
         verify(mAtm.getLifecycleManager()).scheduleTransaction(eq(activity.app.getThread()),
                 eq(activity.token), eq(expected));
+
+        verify(displayRotation).onSetRequestedOrientation();
     }
 
     @Test
@@ -3682,6 +3689,23 @@
         assertTrue(activity.inTransition());
     }
 
+    /**
+     * Verifies the task is moved to back when back pressed if the root activity was originally
+     * started from Launcher.
+     */
+    @Test
+    public void testMoveTaskToBackWhenStartedFromLauncher() {
+        final Task task = createTask(mDisplayContent);
+        final ActivityRecord ar = createActivityRecord(task);
+        task.realActivity = ar.mActivityComponent;
+        ar.intent.setAction(Intent.ACTION_MAIN);
+        ar.intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        doReturn(true).when(ar).isLaunchSourceType(eq(LAUNCH_SOURCE_TYPE_HOME));
+
+        mAtm.mActivityClientController.onBackPressed(ar.token, null /* callback */);
+        verify(task).moveTaskToBack(any());
+    }
+
     private ICompatCameraControlCallback getCompatCameraControlCallback() {
         return new ICompatCameraControlCallback.Stub() {
             @Override
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 e85b574..5282585e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -147,8 +147,8 @@
 
         int width = 100;
         int height = 300;
-        Rect bounds = new Rect(0, 0, width, height);
-        mDimmer.updateDims(mTransaction, bounds);
+        mDimmer.mDimState.mDimBounds.set(0, 0, width, height);
+        mDimmer.updateDims(mTransaction);
 
         verify(mTransaction).setWindowCrop(getDimLayer(), width, height);
         verify(mTransaction).show(getDimLayer());
@@ -194,7 +194,7 @@
         SurfaceControl dimLayer = getDimLayer();
         mDimmer.resetDimStates();
 
-        mDimmer.updateDims(mTransaction, new Rect());
+        mDimmer.updateDims(mTransaction);
         verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any(
                 SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
                 eq(ANIMATION_TYPE_DIMMER));
@@ -212,29 +212,29 @@
         mDimmer.resetDimStates();
         mDimmer.dimAbove(mTransaction, child, alpha);
 
-        mDimmer.updateDims(mTransaction, new Rect());
+        mDimmer.updateDims(mTransaction);
         verify(mTransaction).show(dimLayer);
         verify(mTransaction, never()).remove(dimLayer);
     }
 
     @Test
     public void testDimUpdateWhileDimming() {
-        Rect bounds = new Rect();
         TestWindowContainer child = new TestWindowContainer(mWm);
         mHost.addChild(child, 0);
 
         final float alpha = 0.8f;
         mDimmer.dimAbove(mTransaction, child, alpha);
+        final Rect bounds = mDimmer.mDimState.mDimBounds;
 
         SurfaceControl dimLayer = getDimLayer();
         bounds.set(0, 0, 10, 10);
-        mDimmer.updateDims(mTransaction, bounds);
+        mDimmer.updateDims(mTransaction);
         verify(mTransaction).setWindowCrop(dimLayer, bounds.width(), bounds.height());
         verify(mTransaction, times(1)).show(dimLayer);
         verify(mTransaction).setPosition(dimLayer, 0, 0);
 
         bounds.set(10, 10, 30, 30);
-        mDimmer.updateDims(mTransaction, bounds);
+        mDimmer.updateDims(mTransaction);
         verify(mTransaction).setWindowCrop(dimLayer, bounds.width(), bounds.height());
         verify(mTransaction).setPosition(dimLayer, 10, 10);
     }
@@ -246,13 +246,13 @@
 
         mDimmer.dimAbove(mTransaction, child, 1);
         SurfaceControl dimLayer = getDimLayer();
-        mDimmer.updateDims(mTransaction, new Rect());
+        mDimmer.updateDims(mTransaction);
         verify(mTransaction, times(1)).show(dimLayer);
 
         reset(mSurfaceAnimatorStarter);
         mDimmer.dontAnimateExit();
         mDimmer.resetDimStates();
-        mDimmer.updateDims(mTransaction, new Rect());
+        mDimmer.updateDims(mTransaction);
         verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any(
                 SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
                 eq(ANIMATION_TYPE_DIMMER));
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index ba9f809..7330411 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -24,6 +24,7 @@
 import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
@@ -1063,6 +1064,51 @@
         assertEquals(SCREEN_ORIENTATION_LANDSCAPE, dc.getOrientation());
     }
 
+    private void updateAllDisplayContentAndRotation(DisplayContent dc) {
+        // NB updateOrientation will not revert the user orientation until a settings change
+        // takes effect.
+        dc.updateOrientation();
+        dc.onDisplayChanged(dc);
+        dc.mWmService.updateRotation(true /* alwaysSendConfiguration */,
+                false /* forceRelayout */);
+        waitUntilHandlersIdle();
+    }
+
+    @Test
+    public void testNoSensorRevert() {
+        final DisplayContent dc = mDisplayContent;
+        spyOn(dc);
+        doReturn(true).when(dc).getIgnoreOrientationRequest();
+        final DisplayRotation dr = dc.getDisplayRotation();
+        spyOn(dr);
+        doReturn(false).when(dr).useDefaultSettingsProvider();
+        final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        app.setOrientation(SCREEN_ORIENTATION_LANDSCAPE, app);
+
+        assertFalse(dc.getRotationReversionController().isAnyOverrideActive());
+        dc.getDisplayRotation().setUserRotation(WindowManagerPolicy.USER_ROTATION_LOCKED,
+                ROTATION_90);
+        updateAllDisplayContentAndRotation(dc);
+        assertEquals(ROTATION_90, dc.getDisplayRotation()
+                .rotationForOrientation(SCREEN_ORIENTATION_UNSPECIFIED, ROTATION_90));
+
+        app.setOrientation(SCREEN_ORIENTATION_NOSENSOR);
+        updateAllDisplayContentAndRotation(dc);
+        assertTrue(dc.getRotationReversionController().isAnyOverrideActive());
+        assertEquals(ROTATION_0, dc.getRotation());
+
+        app.setOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
+        updateAllDisplayContentAndRotation(dc);
+        assertFalse(dc.getRotationReversionController().isAnyOverrideActive());
+        assertEquals(WindowManagerPolicy.USER_ROTATION_LOCKED,
+                dc.getDisplayRotation().getUserRotationMode());
+        assertEquals(ROTATION_90, dc.getDisplayRotation().getUserRotation());
+        assertEquals(ROTATION_90, dc.getDisplayRotation()
+                .rotationForOrientation(SCREEN_ORIENTATION_UNSPECIFIED, ROTATION_0));
+        dc.getDisplayRotation().setUserRotation(WindowManagerPolicy.USER_ROTATION_FREE,
+                ROTATION_0);
+    }
+
     @Test
     public void testOnDescendantOrientationRequestChanged() {
         final DisplayContent dc = createNewDisplay();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index c2b3783..a311726 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -365,6 +365,23 @@
     }
 
     @Test
+    public void testCameraDisconnected_revertRotationAndRefresh() throws Exception {
+        configureActivityAndDisplay(SCREEN_ORIENTATION_PORTRAIT, ORIENTATION_LANDSCAPE);
+        // Open camera and test for compat treatment
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+        callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+        assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+                SCREEN_ORIENTATION_LANDSCAPE);
+        assertActivityRefreshRequested(/* refreshRequested */ true);
+        // Close camera and test for revert
+        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+        callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+        assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+                SCREEN_ORIENTATION_UNSPECIFIED);
+        assertActivityRefreshRequested(/* refreshRequested */ true);
+    }
+
+    @Test
     public void testGetOrientation_cameraConnectionClosed_returnUnspecified() {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 495f868..4b2d107 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -70,6 +70,7 @@
 import android.view.Surface;
 import android.view.WindowManager;
 
+import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.test.FakeSettingsProvider;
@@ -114,6 +115,7 @@
 
     private static WindowManagerService sMockWm;
     private DisplayContent mMockDisplayContent;
+    private DisplayRotationReversionController mMockDisplayRotationReversionController;
     private DisplayPolicy mMockDisplayPolicy;
     private DisplayAddress mMockDisplayAddress;
     private Context mMockContext;
@@ -140,6 +142,8 @@
 
     private DeviceStateController mDeviceStateController;
     private TestDisplayRotation mTarget;
+    @Nullable
+    private DisplayRotationImmersiveAppCompatPolicy mDisplayRotationImmersiveAppCompatPolicyMock;
 
     @BeforeClass
     public static void setUpOnce() {
@@ -165,7 +169,7 @@
         LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
         mMockStatusBarManagerInternal = mock(StatusBarManagerInternal.class);
         LocalServices.addService(StatusBarManagerInternal.class, mMockStatusBarManagerInternal);
-
+        mDisplayRotationImmersiveAppCompatPolicyMock = null;
         mBuilder = new DisplayRotationBuilder();
     }
 
@@ -578,6 +582,38 @@
     }
 
     @Test
+    public void testNotifiesChoiceWhenSensorUpdates_immersiveApp() throws Exception {
+        mDisplayRotationImmersiveAppCompatPolicyMock = mock(
+                DisplayRotationImmersiveAppCompatPolicy.class);
+        when(mDisplayRotationImmersiveAppCompatPolicyMock.isRotationLockEnforced(
+                Surface.ROTATION_90)).thenReturn(true);
+
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        thawRotation();
+
+        enableOrientationSensor();
+
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+        assertTrue(waitForUiHandler());
+
+        verify(mMockStatusBarManagerInternal).onProposedRotationChanged(Surface.ROTATION_90, true);
+
+        // An imaginary ActivityRecord.setRequestedOrientation call disables immersive mode:
+        when(mDisplayRotationImmersiveAppCompatPolicyMock.isRotationLockEnforced(
+                Surface.ROTATION_90)).thenReturn(false);
+
+        // And then ActivityRecord.setRequestedOrientation calls onSetRequestedOrientation.
+        mTarget.onSetRequestedOrientation();
+
+        // onSetRequestedOrientation should lead to a second call to
+        // mOrientationListener.onProposedRotationChanged
+        // but now, instead of notifying mMockStatusBarManagerInternal, it calls updateRotation:
+        verify(sMockWm).updateRotation(false, false);
+    }
+
+    @Test
     public void testAllowAllRotations_allowsUpsideDownSuggestion()
             throws Exception {
         mBuilder.build();
@@ -1374,6 +1410,10 @@
             when(mMockContext.getResources().getBoolean(
                     com.android.internal.R.bool.config_windowManagerHalfFoldAutoRotateOverride))
                     .thenReturn(mSupportHalfFoldAutoRotateOverride);
+            mMockDisplayRotationReversionController =
+                    mock(DisplayRotationReversionController.class);
+            when(mMockDisplayContent.getRotationReversionController())
+                        .thenReturn(mMockDisplayRotationReversionController);
 
             mMockResolver = mock(ContentResolver.class);
             when(mMockContext.getContentResolver()).thenReturn(mMockResolver);
@@ -1404,7 +1444,7 @@
         }
     }
 
-    private static class TestDisplayRotation extends DisplayRotation {
+    private class TestDisplayRotation extends DisplayRotation {
         IntConsumer mProposedRotationCallback;
 
         TestDisplayRotation(DisplayContent dc, DisplayAddress address, DisplayPolicy policy,
@@ -1417,7 +1457,7 @@
         @Override
         DisplayRotationImmersiveAppCompatPolicy initImmersiveAppCompatPolicy(
                 WindowManagerService service, DisplayContent displayContent) {
-            return null;
+            return mDisplayRotationImmersiveAppCompatPolicyMock;
         }
 
         @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index 43fc1c4..7cb7c79d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -111,7 +111,6 @@
         mDisplayUniqueId = "test:" + sNextUniqueId++;
         mTestDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1500)
                 .setUniqueId(mDisplayUniqueId).build();
-        mTestDisplay.getDefaultTaskDisplayArea().setWindowingMode(TEST_WINDOWING_MODE);
         when(mRootWindowContainer.getDisplayContent(eq(mDisplayUniqueId)))
                 .thenReturn(mTestDisplay);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 9ebc730..10f4158 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -924,6 +924,11 @@
 
     @Test
     public void testFreezeTaskListOrder_timeout() {
+        for (Task t : mTasks) {
+            // Make all the tasks non-empty
+            new ActivityBuilder(mAtm).setTask(t).build();
+        }
+
         // Add some tasks
         mRecentTasks.add(mTasks.get(0));
         mRecentTasks.add(mTasks.get(1));
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index e96d1ab..de943d2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -428,20 +429,24 @@
                 .setLaunchedFromUid(mActivity.getUid())
                 .build();
         doReturn(false).when(translucentActivity).fillsParent();
-        WindowConfiguration translucentWinConf = translucentActivity.getWindowConfiguration();
-        translucentActivity.setActivityType(ACTIVITY_TYPE_STANDARD);
-        translucentActivity.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
-        translucentActivity.setDisplayWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
-        translucentActivity.setAlwaysOnTop(true);
+        final Configuration requestedConfig =
+                translucentActivity.getRequestedOverrideConfiguration();
+        final WindowConfiguration translucentWinConf = requestedConfig.windowConfiguration;
+        translucentWinConf.setActivityType(ACTIVITY_TYPE_STANDARD);
+        translucentWinConf.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        translucentWinConf.setDisplayWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        translucentWinConf.setAlwaysOnTop(true);
+        translucentActivity.onRequestedOverrideConfigurationChanged(requestedConfig);
 
         mTask.addChild(translucentActivity);
 
-        // We check the WIndowConfiguration properties
-        translucentWinConf = translucentActivity.getWindowConfiguration();
+        // The original override of WindowConfiguration should keep.
         assertEquals(ACTIVITY_TYPE_STANDARD, translucentActivity.getActivityType());
         assertEquals(WINDOWING_MODE_MULTI_WINDOW, translucentWinConf.getWindowingMode());
         assertEquals(WINDOWING_MODE_MULTI_WINDOW, translucentWinConf.getDisplayWindowingMode());
         assertTrue(translucentWinConf.isAlwaysOnTop());
+        // Unless display is going to be rotated, it should always inherit from parent.
+        assertEquals(ROTATION_UNDEFINED, translucentWinConf.getDisplayRotation());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index 8e91ca2..77efc4b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -42,6 +42,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.testutils.TestHandler;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -371,6 +373,49 @@
         mAppWindow.removeImmediately();
     }
 
+    @Test
+    public void testQueueSyncSet() {
+        final TestHandler testHandler = new TestHandler(null);
+        TestWindowContainer mockWC = new TestWindowContainer(mWm, true /* waiter */);
+        TestWindowContainer mockWC2 = new TestWindowContainer(mWm, true /* waiter */);
+
+        final BLASTSyncEngine bse = createTestBLASTSyncEngine(testHandler);
+
+        BLASTSyncEngine.TransactionReadyListener listener = mock(
+                BLASTSyncEngine.TransactionReadyListener.class);
+
+        int id = startSyncSet(bse, listener);
+        bse.addToSyncSet(id, mockWC);
+        bse.setReady(id);
+        bse.onSurfacePlacement();
+        verify(listener, times(0)).onTransactionReady(eq(id), notNull());
+
+        final int[] nextId = new int[]{-1};
+        bse.queueSyncSet(
+                () -> nextId[0] = startSyncSet(bse, listener),
+                () -> {
+                    bse.setReady(nextId[0]);
+                    bse.addToSyncSet(nextId[0], mockWC2);
+                });
+
+        // Make sure it is queued
+        assertEquals(-1, nextId[0]);
+
+        // Finish the original sync and see that we've started a new sync-set immediately but
+        // that the readiness was posted.
+        mockWC.onSyncFinishedDrawing();
+        verify(mWm.mWindowPlacerLocked).requestTraversal();
+        bse.onSurfacePlacement();
+        verify(listener, times(1)).onTransactionReady(eq(id), notNull());
+
+        assertTrue(nextId[0] != -1);
+        assertFalse(bse.isReady(nextId[0]));
+
+        // now make sure the applySync callback was posted.
+        testHandler.flush();
+        assertTrue(bse.isReady(nextId[0]));
+    }
+
     static int startSyncSet(BLASTSyncEngine engine,
             BLASTSyncEngine.TransactionReadyListener listener) {
         return engine.startSyncSet(listener, BLAST_TIMEOUT_DURATION, "Test");
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 90506d4..1e2fdec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1404,8 +1404,6 @@
         // We are now going to simulate closing task1 to return back to (open) task2.
         final Transition closeTransition = controller.createTransition(TRANSIT_CLOSE);
 
-        closeTransition.collectExistenceChange(task1);
-        closeTransition.collectExistenceChange(activity1);
         closeTransition.collectExistenceChange(task2);
         closeTransition.collectExistenceChange(activity2);
         closeTransition.setTransientLaunch(activity2, task1);
@@ -1416,7 +1414,7 @@
         assertNotNull(activity1ChangeInfo);
         assertTrue(activity1ChangeInfo.hasChanged());
         // No need to wait for the activity in transient hide task.
-        assertTrue(activity1.isSyncFinished());
+        assertEquals(WindowContainer.SYNC_STATE_NONE, activity1.mSyncState);
 
         activity1.setVisibleRequested(false);
         activity2.setVisibleRequested(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index b48fd7d..fdb3502 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -691,6 +691,7 @@
         // Child window without scale (e.g. different app) should apply inverse scale of parent.
         doReturn(1f).when(cmp).getCompatScale(anyString(), anyInt());
         final WindowState child2 = createWindow(w, TYPE_APPLICATION_SUB_PANEL, "child2");
+        makeWindowVisible(w, child2);
         clearInvocations(t);
         child2.prepareSurfaces();
         verify(t).setMatrix(child2.mSurfaceControl, w.mInvGlobalScale, 0, 0, w.mInvGlobalScale);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 7e3ec55..f85cdf0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -77,6 +77,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -886,7 +887,11 @@
     }
 
     BLASTSyncEngine createTestBLASTSyncEngine() {
-        return new BLASTSyncEngine(mWm) {
+        return createTestBLASTSyncEngine(mWm.mH);
+    }
+
+    BLASTSyncEngine createTestBLASTSyncEngine(Handler handler) {
+        return new BLASTSyncEngine(mWm, handler) {
             @Override
             void scheduleTimeout(SyncGroup s, long timeoutMs) {
                 // Disable timeout.
diff --git a/services/voiceinteraction/Android.bp b/services/voiceinteraction/Android.bp
index 7332d2d..de8d144 100644
--- a/services/voiceinteraction/Android.bp
+++ b/services/voiceinteraction/Android.bp
@@ -9,11 +9,60 @@
 
 filegroup {
     name: "services.voiceinteraction-sources",
-    srcs: ["java/**/*.java"],
+    srcs: ["java/com/android/server/voiceinteraction/*.java"],
     path: "java",
     visibility: ["//frameworks/base/services"],
 }
 
+filegroup {
+    name: "services.soundtrigger_middleware-sources",
+    srcs: ["java/com/android/server/soundtrigger_middleware/*.java"],
+    path: "java",
+    visibility: ["//visibility:private"],
+}
+
+filegroup {
+    name: "services.soundtrigger_service-sources",
+    srcs: ["java/com/android/server/soundtrigger/*.java"],
+    path: "java",
+    visibility: ["//visibility:private"],
+}
+
+filegroup {
+    name: "services.soundtrigger-sources",
+    srcs: [
+        ":services.soundtrigger_service-sources",
+        ":services.soundtrigger_middleware-sources",
+    ],
+    path: "java",
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+    name: "services.soundtrigger_middleware",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.soundtrigger_middleware-sources"],
+    libs: [
+        "services.core",
+    ],
+    static_libs: [
+        "android.hardware.soundtrigger-V2.3-java",
+    ],
+    visibility: ["//visibility/base/services/tests/voiceinteraction"],
+}
+
+java_library_static {
+    name: "services.soundtrigger",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.soundtrigger_service-sources"],
+    libs: [
+        "services.core",
+    ],
+    static_libs: [
+        "services.soundtrigger_middleware",
+    ],
+}
+
 java_library_static {
     name: "services.voiceinteraction",
     defaults: ["platform_service_defaults"],
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 04c1c04..203a3e7 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -39,12 +39,13 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.PermissionChecker;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.hardware.soundtrigger.ConversionUtil;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.ModelParams;
-import android.hardware.soundtrigger.ConversionUtil;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
@@ -64,6 +65,7 @@
 import android.media.soundtrigger.ISoundTriggerDetectionService;
 import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
 import android.media.soundtrigger.SoundTriggerDetectionService;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.os.Binder;
 import android.os.Bundle;
@@ -74,8 +76,8 @@
 import android.os.ParcelUuid;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.ServiceSpecificException;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -86,6 +88,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.ISoundTriggerService;
 import com.android.internal.app.ISoundTriggerSession;
+import com.android.server.SoundTriggerInternal;
 import com.android.server.SystemService;
 import com.android.server.utils.EventLogger;
 
@@ -98,8 +101,8 @@
 import java.util.Objects;
 import java.util.TreeMap;
 import java.util.UUID;
-import java.util.stream.Collectors;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * A single SystemService to manage all sound/voice-based sound models on the DSP.
@@ -296,6 +299,23 @@
                 return listUnderlyingModuleProperties(originatorIdentity);
             }
         }
+
+        @Override
+        public void attachInjection(@NonNull ISoundTriggerInjection injection) {
+            if (PermissionChecker.checkCallingPermissionForPreflight(mContext,
+                    android.Manifest.permission.MANAGE_SOUND_TRIGGER, null)
+                        != PermissionChecker.PERMISSION_GRANTED) {
+                throw new SecurityException();
+            }
+            try {
+                ISoundTriggerMiddlewareService.Stub
+                        .asInterface(ServiceManager
+                                .waitForService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE))
+                        .attachFakeHalInjection(injection);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     class SoundTriggerSessionStub extends ISoundTriggerSession.Stub {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index aaf7a9e..5846ff6 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -39,7 +39,7 @@
  *
  * @hide
  */
-public class DatabaseHelper extends SQLiteOpenHelper {
+public class DatabaseHelper extends SQLiteOpenHelper implements IEnrolledModelDb {
     static final String TAG = "SoundModelDBHelper";
     static final boolean DBG = false;
 
@@ -153,11 +153,7 @@
         }
     }
 
-    /**
-     * Updates the given keyphrase model, adds it, if it doesn't already exist.
-     *
-     * TODO: We only support one keyphrase currently.
-     */
+    @Override
     public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
         synchronized(this) {
             SQLiteDatabase db = getWritableDatabase();
@@ -193,9 +189,7 @@
         }
     }
 
-    /**
-     * Deletes the sound model and associated keyphrases.
-     */
+    @Override
     public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) {
         // Normalize the locale to guard against SQL injection.
         bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag();
@@ -218,12 +212,7 @@
         }
     }
 
-    /**
-     * Returns a matching {@link KeyphraseSoundModel} for the keyphrase ID.
-     * Returns null if a match isn't found.
-     *
-     * TODO: We only support one keyphrase currently.
-     */
+    @Override
     public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
             String bcp47Locale) {
         // Sanitize the locale to guard against SQL injection.
@@ -237,12 +226,7 @@
         }
     }
 
-    /**
-     * Returns a matching {@link KeyphraseSoundModel} for the keyphrase string.
-     * Returns null if a match isn't found.
-     *
-     * TODO: We only support one keyphrase currently.
-     */
+    @Override
     public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
             String bcp47Locale) {
         // Sanitize the locale to guard against SQL injection.
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index f3cb9ba..e6dedc10 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -153,7 +153,8 @@
     private int mRestartCount = 0;
     @NonNull private ServiceConnection mRemoteHotwordDetectionService;
     @NonNull private ServiceConnection mRemoteVisualQueryDetectionService;
-    private IBinder mAudioFlinger;
+    @GuardedBy("mLock")
+    @Nullable private IBinder mAudioFlinger;
     @GuardedBy("mLock")
     private boolean mDebugHotwordLogging = false;
 
@@ -193,7 +194,7 @@
                 new Intent(VisualQueryDetectionService.SERVICE_INTERFACE);
         visualQueryDetectionServiceIntent.setComponent(mVisualQueryDetectionComponentName);
 
-        initAudioFlingerLocked();
+        initAudioFlinger();
 
         mHotwordDetectionServiceConnectionFactory =
                 new ServiceConnectionFactory(hotwordDetectionServiceIntent,
@@ -226,31 +227,41 @@
         }
     }
 
-    private void initAudioFlingerLocked() {
+    private void initAudioFlinger() {
         if (DEBUG) {
-            Slog.d(TAG, "initAudioFlingerLocked");
+            Slog.d(TAG, "initAudioFlinger");
         }
-        mAudioFlinger = ServiceManager.waitForService("media.audio_flinger");
-        if (mAudioFlinger == null) {
+        final IBinder audioFlinger = ServiceManager.waitForService("media.audio_flinger");
+        if (audioFlinger == null) {
+            setAudioFlinger(null);
             throw new IllegalStateException("Service media.audio_flinger wasn't found.");
         }
         if (DEBUG) {
             Slog.d(TAG, "Obtained audio_flinger binder.");
         }
         try {
-            mAudioFlinger.linkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
+            audioFlinger.linkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
         } catch (RemoteException e) {
             Slog.w(TAG, "Audio server died before we registered a DeathRecipient; "
-                            + "retrying init.", e);
-            initAudioFlingerLocked();
+                    + "retrying init.", e);
+            initAudioFlinger();
+            return;
+        }
+
+        setAudioFlinger(audioFlinger);
+    }
+
+    private void setAudioFlinger(@Nullable IBinder audioFlinger) {
+        synchronized (mLock) {
+            mAudioFlinger = audioFlinger;
         }
     }
 
     private void audioServerDied() {
         Slog.w(TAG, "Audio server died; restarting the HotwordDetectionService.");
+        // TODO: Check if this needs to be scheduled on a different thread.
+        initAudioFlinger();
         synchronized (mLock) {
-            // TODO: Check if this needs to be scheduled on a different thread.
-            initAudioFlingerLocked();
             // We restart the process instead of simply sending over the new binder, to avoid race
             // conditions with audio reading in the service.
             restartProcessLocked();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java
new file mode 100644
index 0000000..f10c2f6
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.voiceinteraction;
+
+import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface for registering and querying the enrolled keyphrase model database for
+ * {@link VoiceInteractionManagerService}.
+ * This interface only supports one keyphrase per {@link KeyphraseSoundModel}.
+ * The non-update methods are uniquely keyed on fields of the first keyphrase
+ * {@link KeyphraseSoundModel#getKeyphrases()}.
+ * @hide
+ */
+public interface IEnrolledModelDb {
+
+    //TODO(273286174): We only support one keyphrase currently.
+    /**
+     * Register the given {@link KeyphraseSoundModel}, or updates it if it already exists.
+     *
+     * @param soundModel - The sound model to register in the database.
+     * Updates the sound model if the keyphrase id, users, locale match an existing entry.
+     * Must have one and only one associated {@link Keyphrase}.
+     * @return - {@code true} if successful, {@code false} if unsuccessful
+     */
+    boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel);
+
+    /**
+     * Deletes the previously registered keyphrase sound model from the database.
+     *
+     * @param keyphraseId - The (first) keyphrase ID of the KeyphraseSoundModel to delete.
+     * @param userHandle - The user handle making this request. Must be included in the user
+     *                     list of the registered sound model.
+     * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+     * @return - {@code true} if successful, {@code false} if unsuccessful
+     */
+    boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale);
+
+    //TODO(273286174): We only support one keyphrase currently.
+    /**
+     * Returns the first matching {@link KeyphraseSoundModel} for the keyphrase ID, locale pair,
+     * contingent on the userHandle existing in the user list for the model.
+     * Returns null if a match isn't found.
+     *
+     * @param keyphraseId - The (first) keyphrase ID of the KeyphraseSoundModel to query.
+     * @param userHandle - The user handle making this request. Must be included in the user
+     *                     list of the registered sound model.
+     * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+     * @return - {@code true} if successful, {@code false} if unsuccessful
+     */
+    KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
+            String bcp47Locale);
+
+    //TODO(273286174): We only support one keyphrase currently.
+    /**
+     * Returns the first matching {@link KeyphraseSoundModel} for the keyphrase ID, locale pair,
+     * contingent on the userHandle existing in the user list for the model.
+     * Returns null if a match isn't found.
+     *
+     * @param keyphrase - The text of (the first) keyphrase of the KeyphraseSoundModel to query.
+     * @param userHandle - The user handle making this request. Must be included in the user
+     *                     list of the registered sound model.
+     * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+     * @return - {@code true} if successful, {@code false} if unsuccessful
+     */
+    KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
+            String bcp47Locale);
+
+    /**
+     * Dumps contents of database for dumpsys
+     */
+    void dump(PrintWriter pw);
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java
new file mode 100644
index 0000000..9bbaf8e
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java
@@ -0,0 +1,148 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.voiceinteraction;
+
+import android.annotation.NonNull;
+import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+/**
+ * In memory model enrollment database for testing purposes.
+ * @hide
+ */
+public class TestModelEnrollmentDatabase implements IEnrolledModelDb {
+
+    // Record representing the primary key used in the real model database.
+    private static final class EnrollmentKey {
+        private final int mKeyphraseId;
+        private final List<Integer> mUserIds;
+        private final String mLocale;
+
+        EnrollmentKey(int keyphraseId,
+                @NonNull List<Integer> userIds, @NonNull String locale) {
+            mKeyphraseId = keyphraseId;
+            mUserIds = Objects.requireNonNull(userIds);
+            mLocale = Objects.requireNonNull(locale);
+        }
+
+        int keyphraseId() {
+            return mKeyphraseId;
+        }
+
+        List<Integer> userIds() {
+            return mUserIds;
+        }
+
+        String locale() {
+            return mLocale;
+        }
+
+        @Override
+        public String toString() {
+            StringJoiner sj = new StringJoiner(", ", "{", "}");
+            sj.add("keyphraseId: " + mKeyphraseId);
+            sj.add("userIds: " + mUserIds.toString());
+            sj.add("locale: " + mLocale.toString());
+            return "EnrollmentKey: " + sj.toString();
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int res = 1;
+            res = prime * res + mKeyphraseId;
+            res = prime * res + mUserIds.hashCode();
+            res = prime * res + mLocale.hashCode();
+            return res;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) return true;
+            if (other == null) return false;
+            if (!(other instanceof EnrollmentKey)) return false;
+            EnrollmentKey that = (EnrollmentKey) other;
+            if (mKeyphraseId != that.mKeyphraseId) return false;
+            if (!mUserIds.equals(that.mUserIds)) return false;
+            if (!mLocale.equals(that.mLocale)) return false;
+            return true;
+        }
+
+    }
+
+    private final Map<EnrollmentKey, KeyphraseSoundModel> mModelMap = new HashMap<>();
+
+    @Override
+    public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
+        final Keyphrase keyphrase = soundModel.getKeyphrases()[0];
+        mModelMap.put(new EnrollmentKey(keyphrase.getId(),
+                        Arrays.stream(keyphrase.getUsers()).boxed().toList(),
+                        keyphrase.getLocale().toLanguageTag()),
+                    soundModel);
+        return true;
+    }
+
+    @Override
+    public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) {
+        return mModelMap.keySet().removeIf(key -> (key.keyphraseId() == keyphraseId)
+                && key.locale().equals(bcp47Locale)
+                && key.userIds().contains(userHandle));
+    }
+
+    @Override
+    public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
+            String bcp47Locale) {
+        return mModelMap.entrySet()
+                .stream()
+                .filter((entry) -> (entry.getKey().keyphraseId() == keyphraseId)
+                        && entry.getKey().locale().equals(bcp47Locale)
+                        && entry.getKey().userIds().contains(userHandle))
+                .findFirst()
+                .map((entry) -> entry.getValue())
+                .orElse(null);
+    }
+
+    @Override
+    public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
+            String bcp47Locale) {
+        return mModelMap.entrySet()
+                .stream()
+                .filter((entry) -> (entry.getValue().getKeyphrases()[0].getText().equals(keyphrase)
+                        && entry.getKey().locale().equals(bcp47Locale)
+                        && entry.getKey().userIds().contains(userHandle)))
+                .findFirst()
+                .map((entry) -> entry.getValue())
+                .orElse(null);
+    }
+
+
+    /**
+     * Dumps contents of database for dumpsys
+     */
+    public void dump(PrintWriter pw) {
+        pw.println("Using test enrollment database, with enrolled models:");
+        pw.println(mModelMap);
+    }
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index e1da2ca..1d7b966 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -99,11 +99,11 @@
 import com.android.internal.util.DumpUtils;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.SoundTriggerInternal;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
-import com.android.server.soundtrigger.SoundTriggerInternal;
 import com.android.server.utils.Slogf;
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -125,7 +125,9 @@
 
     final Context mContext;
     final ContentResolver mResolver;
-    final DatabaseHelper mDbHelper;
+    // Can be overridden for testing purposes
+    private IEnrolledModelDb mDbHelper;
+    private final IEnrolledModelDb mRealDbHelper;
     final ActivityManagerInternal mAmInternal;
     final ActivityTaskManagerInternal mAtmInternal;
     final UserManagerInternal mUserManagerInternal;
@@ -143,7 +145,7 @@
         mResolver = context.getContentResolver();
         mUserManagerInternal = Objects.requireNonNull(
                 LocalServices.getService(UserManagerInternal.class));
-        mDbHelper = new DatabaseHelper(context);
+        mDbHelper = mRealDbHelper = new DatabaseHelper(context);
         mServiceStub = new VoiceInteractionManagerServiceStub();
         mAmInternal = Objects.requireNonNull(
                 LocalServices.getService(ActivityManagerInternal.class));
@@ -1605,6 +1607,42 @@
             }
         }
 
+        @Override
+        @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+        public void setModelDatabaseForTestEnabled(boolean enabled, IBinder token) {
+            super.setModelDatabaseForTestEnabled_enforcePermission();
+            enforceCallerAllowedToEnrollVoiceModel();
+            synchronized (this) {
+                if (enabled) {
+                    // Replace the dbhelper with a new test db
+                    final var db = new TestModelEnrollmentDatabase();
+                    try {
+                        // Listen to our caller death, and make sure we revert to the real
+                        // db if they left the model in a test state.
+                        token.linkToDeath(() -> {
+                            synchronized (this) {
+                                if (mDbHelper == db) {
+                                    mDbHelper = mRealDbHelper;
+                                    mImpl.notifySoundModelsChangedLocked();
+                                }
+                            }
+                        }, 0);
+                    } catch (RemoteException e) {
+                        // If the caller is already dead, nothing to do.
+                        return;
+                    }
+                    mDbHelper = db;
+                    mImpl.notifySoundModelsChangedLocked();
+                } else {
+                    // Nothing to do if the db is already set to the real impl.
+                    if (mDbHelper != mRealDbHelper) {
+                        mDbHelper = mRealDbHelper;
+                        mImpl.notifySoundModelsChangedLocked();
+                    }
+                }
+            }
+        }
+
         //----------------- SoundTrigger APIs --------------------------------//
         @Override
         public boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale) {
@@ -1712,28 +1750,27 @@
                 final long caller = Binder.clearCallingIdentity();
                 try {
                     KeyphraseSoundModel soundModel =
-                            mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUserId, bcp47Locale);
+                            mDbHelper.getKeyphraseSoundModel(keyphraseId,
+                                    callingUserId, bcp47Locale);
                     if (soundModel == null
                             || soundModel.getUuid() == null
                             || soundModel.getKeyphrases() == null) {
                         Slog.w(TAG, "No matching sound model found in startRecognition");
                         return SoundTriggerInternal.STATUS_ERROR;
-                    } else {
-                        // Regardless of the status of the start recognition, we need to make sure
-                        // that we unload this model if needed later.
-                        synchronized (VoiceInteractionManagerServiceStub.this) {
-                            mLoadedKeyphraseIds.put(keyphraseId, this);
-                            if (mSessionExternalCallback == null
-                                    || mSessionInternalCallback == null
-                                    || callback.asBinder() != mSessionExternalCallback.asBinder()) {
-                                mSessionInternalCallback = createSoundTriggerCallbackLocked(
-                                        callback);
-                                mSessionExternalCallback = callback;
-                            }
-                        }
-                        return mSession.startRecognition(keyphraseId, soundModel,
-                                mSessionInternalCallback, recognitionConfig, runInBatterySaverMode);
                     }
+                    // Regardless of the status of the start recognition, we need to make sure
+                    // that we unload this model if needed later.
+                    synchronized (VoiceInteractionManagerServiceStub.this) {
+                        mLoadedKeyphraseIds.put(keyphraseId, this);
+                        if (mSessionExternalCallback == null
+                                || mSessionInternalCallback == null
+                                || callback.asBinder() != mSessionExternalCallback.asBinder()) {
+                            mSessionInternalCallback = createSoundTriggerCallbackLocked(callback);
+                            mSessionExternalCallback = callback;
+                        }
+                    }
+                    return mSession.startRecognition(keyphraseId, soundModel,
+                            mSessionInternalCallback, recognitionConfig, runInBatterySaverMode);
                 } finally {
                     Binder.restoreCallingIdentity(caller);
                 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 62be2a55..0ad86c1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -71,7 +71,6 @@
 import android.util.Slog;
 import android.view.IWindowManager;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
 import com.android.internal.app.IVisualQueryDetectionAttentionListener;
 import com.android.internal.app.IVoiceActionCheckCallback;
@@ -248,7 +247,6 @@
                 Context.RECEIVER_EXPORTED);
     }
 
-    @GuardedBy("this")
     public void grantImplicitAccessLocked(int grantRecipientUid, @Nullable Intent intent) {
         final int grantRecipientAppId = UserHandle.getAppId(grantRecipientUid);
         final int grantRecipientUserId = UserHandle.getUserId(grantRecipientUid);
@@ -258,7 +256,6 @@
                 /* direct= */ true);
     }
 
-    @GuardedBy("this")
     public boolean showSessionLocked(@Nullable Bundle args, int flags,
             @Nullable String attributionTag,
             @Nullable IVoiceInteractionSessionShowCallback showCallback,
@@ -331,7 +328,6 @@
         }
     }
 
-    @GuardedBy("this")
     public boolean hideSessionLocked() {
         if (mActiveSession != null) {
             return mActiveSession.hideLocked();
@@ -339,7 +335,6 @@
         return false;
     }
 
-    @GuardedBy("this")
     public boolean deliverNewSessionLocked(IBinder token,
             IVoiceInteractionSession session, IVoiceInteractor interactor) {
         if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -350,7 +345,6 @@
         return true;
     }
 
-    @GuardedBy("this")
     public int startVoiceActivityLocked(@Nullable String callingFeatureId, int callingPid,
             int callingUid, IBinder token, Intent intent, String resolvedType) {
         try {
@@ -373,7 +367,6 @@
         }
     }
 
-    @GuardedBy("this")
     public int startAssistantActivityLocked(@Nullable String callingFeatureId, int callingPid,
             int callingUid, IBinder token, Intent intent, String resolvedType,
             @NonNull Bundle bundle) {
@@ -397,7 +390,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void requestDirectActionsLocked(@NonNull IBinder token, int taskId,
             @NonNull IBinder assistToken,  @Nullable RemoteCallback cancellationCallback,
             @NonNull RemoteCallback callback) {
@@ -453,7 +445,6 @@
         }
     }
 
-    @GuardedBy("this")
     void performDirectActionLocked(@NonNull IBinder token, @NonNull String actionId,
             @Nullable Bundle arguments, int taskId, IBinder assistToken,
             @Nullable RemoteCallback cancellationCallback,
@@ -480,7 +471,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void setKeepAwakeLocked(IBinder token, boolean keepAwake) {
         try {
             if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -493,7 +483,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void closeSystemDialogsLocked(IBinder token) {
         try {
             if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -506,7 +495,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void finishLocked(IBinder token, boolean finishTask) {
         if (mActiveSession == null || (!finishTask && token != mActiveSession.mToken)) {
             Slog.w(TAG, "finish does not match active session");
@@ -516,7 +504,6 @@
         mActiveSession = null;
     }
 
-    @GuardedBy("this")
     public void setDisabledShowContextLocked(int callingUid, int flags) {
         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
         if (callingUid != activeUid) {
@@ -526,7 +513,6 @@
         mDisabledShowContext = flags;
     }
 
-    @GuardedBy("this")
     public int getDisabledShowContextLocked(int callingUid) {
         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
         if (callingUid != activeUid) {
@@ -536,7 +522,6 @@
         return mDisabledShowContext;
     }
 
-    @GuardedBy("this")
     public int getUserDisabledShowContextLocked(int callingUid) {
         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
         if (callingUid != activeUid) {
@@ -550,7 +535,6 @@
         return mInfo.getSupportsLocalInteraction();
     }
 
-    @GuardedBy("this")
     public void startListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
         if (DEBUG) {
             Slog.d(TAG, "startListeningVisibleActivityChangedLocked: token=" + token);
@@ -563,7 +547,6 @@
         mActiveSession.startListeningVisibleActivityChangedLocked();
     }
 
-    @GuardedBy("this")
     public void stopListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
         if (DEBUG) {
             Slog.d(TAG, "stopListeningVisibleActivityChangedLocked: token=" + token);
@@ -576,7 +559,6 @@
         mActiveSession.stopListeningVisibleActivityChangedLocked();
     }
 
-    @GuardedBy("this")
     public void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
         if (DEBUG) {
             Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
@@ -591,7 +573,6 @@
         mActiveSession.notifyActivityDestroyedLocked(activityToken);
     }
 
-    @GuardedBy("this")
     public void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
         if (DEBUG) {
             Slog.d(TAG, "notifyActivityEventChangedLocked type=" + type);
@@ -606,7 +587,6 @@
         mActiveSession.notifyActivityEventChangedLocked(activityToken, type);
     }
 
-    @GuardedBy("this")
     public void updateStateLocked(
             @Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory,
@@ -627,7 +607,6 @@
         }
     }
 
-    @GuardedBy("this")
     private void verifyDetectorForHotwordDetectionLocked(
             @Nullable SharedMemory sharedMemory,
             IHotwordRecognitionStatusCallback callback,
@@ -685,7 +664,6 @@
                 voiceInteractionServiceUid);
     }
 
-    @GuardedBy("this")
     private void verifyDetectorForVisualQueryDetectionLocked(@Nullable SharedMemory sharedMemory) {
         Slog.v(TAG, "verifyDetectorForVisualQueryDetectionLocked");
 
@@ -724,7 +702,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void initAndVerifyDetectorLocked(
             @NonNull Identity voiceInteractorIdentity,
             @Nullable PersistableBundle options,
@@ -769,7 +746,6 @@
                 detectorType);
     }
 
-    @GuardedBy("this")
     public void destroyDetectorLocked(IBinder token) {
         Slog.v(TAG, "destroyDetectorLocked");
 
@@ -788,7 +764,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void shutdownHotwordDetectionServiceLocked() {
         if (DEBUG) {
             Slog.d(TAG, "shutdownHotwordDetectionServiceLocked");
@@ -801,7 +776,6 @@
         mHotwordDetectionConnection = null;
     }
 
-    @GuardedBy("this")
     public void setVisualQueryDetectionAttentionListenerLocked(
             @Nullable IVisualQueryDetectionAttentionListener listener) {
         if (mHotwordDetectionConnection == null) {
@@ -810,7 +784,6 @@
         mHotwordDetectionConnection.setVisualQueryDetectionAttentionListenerLocked(listener);
     }
 
-    @GuardedBy("this")
     public void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "startPerceivingLocked");
@@ -824,7 +797,6 @@
         mHotwordDetectionConnection.startPerceivingLocked(callback);
     }
 
-    @GuardedBy("this")
     public void stopPerceivingLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopPerceivingLocked");
@@ -838,7 +810,6 @@
         mHotwordDetectionConnection.stopPerceivingLocked();
     }
 
-    @GuardedBy("this")
     public void startListeningFromMicLocked(
             AudioFormat audioFormat,
             IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
@@ -854,7 +825,6 @@
         mHotwordDetectionConnection.startListeningFromMicLocked(audioFormat, callback);
     }
 
-    @GuardedBy("this")
     public void startListeningFromExternalSourceLocked(
             ParcelFileDescriptor audioStream,
             AudioFormat audioFormat,
@@ -879,7 +849,6 @@
                 options, token, callback);
     }
 
-    @GuardedBy("this")
     public void stopListeningFromMicLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopListeningFromMicLocked");
@@ -893,7 +862,6 @@
         mHotwordDetectionConnection.stopListeningFromMicLocked();
     }
 
-    @GuardedBy("this")
     public void triggerHardwareRecognitionEventForTestLocked(
             SoundTrigger.KeyphraseRecognitionEvent event,
             IHotwordRecognitionStatusCallback callback) {
@@ -908,7 +876,6 @@
         mHotwordDetectionConnection.triggerHardwareRecognitionEventForTestLocked(event, callback);
     }
 
-    @GuardedBy("this")
     public IRecognitionStatusCallback createSoundTriggerCallbackLocked(
             IHotwordRecognitionStatusCallback callback) {
         if (DEBUG) {
@@ -933,12 +900,11 @@
         return null;
     }
 
-    @GuardedBy("this")
     boolean isIsolatedProcessLocked(@NonNull ServiceInfo serviceInfo) {
         return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
                 && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
     }
-    @GuardedBy("this")
+
     boolean verifyProcessSharingLocked() {
         // only check this if both VQDS and HDS are declared in the app
         ServiceInfo hotwordInfo = getServiceInfoLocked(mHotwordDetectionComponentName, mUser);
@@ -960,7 +926,6 @@
         mHotwordDetectionConnection.forceRestart();
     }
 
-    @GuardedBy("this")
     void setDebugHotwordLoggingLocked(boolean logging) {
         if (mHotwordDetectionConnection == null) {
             Slog.w(TAG, "Failed to set temporary debug logging: no hotword detection active");
@@ -969,7 +934,6 @@
         mHotwordDetectionConnection.setDebugHotwordLoggingLocked(logging);
     }
 
-    @GuardedBy("this")
     void resetHotwordDetectionConnectionLocked() {
         if (DEBUG) {
             Slog.d(TAG, "resetHotwordDetectionConnectionLocked");
@@ -984,7 +948,6 @@
         mHotwordDetectionConnection = null;
     }
 
-    @GuardedBy("this")
     public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!mValid) {
             pw.print("  NOT VALID: ");
@@ -1023,7 +986,6 @@
         }
     }
 
-    @GuardedBy("this")
     void startLocked() {
         Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
         intent.setComponent(mComponent);
@@ -1048,7 +1010,6 @@
         }
     }
 
-    @GuardedBy("this")
     void shutdownLocked() {
         // If there is an active session, cancel it to allow it to clean up its window and other
         // state.
@@ -1076,7 +1037,6 @@
         }
     }
 
-    @GuardedBy("this")
     void notifySoundModelsChangedLocked() {
         if (mService == null) {
             Slog.w(TAG, "Not bound to voice interaction service " + mComponent);
diff --git a/telecomm/java/android/telecom/CallAttributes.java b/telecomm/java/android/telecom/CallAttributes.java
index f3ef834..52ff90f 100644
--- a/telecomm/java/android/telecom/CallAttributes.java
+++ b/telecomm/java/android/telecom/CallAttributes.java
@@ -59,7 +59,10 @@
     public static final String CALL_CAPABILITIES_KEY = "TelecomCapabilities";
 
     /** @hide **/
-    public static final String CALLER_PID = "CallerPid";
+    public static final String CALLER_PID_KEY = "CallerPid";
+
+    /** @hide **/
+    public static final String CALLER_UID_KEY = "CallerUid";
 
     private CallAttributes(@NonNull PhoneAccountHandle phoneAccountHandle,
             @NonNull CharSequence displayName,
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index e39af5a..9dd2a61 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2682,71 +2682,76 @@
     }
 
     /**
-     * Reports a new call with the specified {@link CallAttributes} to the telecom service. This
-     * method can be used to report both incoming and outgoing calls.  By reporting the call, the
-     * system is aware of the call and can provide updates on services (ex. Another device wants to
-     * disconnect the call) or events (ex. a new Bluetooth route became available).
-     *
+     * Add a call to the Android system service Telecom. This allows the system to start tracking an
+     * incoming or outgoing call with the specified {@link CallAttributes}. Once the call is ready
+     * to be disconnected, use the {@link CallControl#disconnect(DisconnectCause, Executor,
+     * OutcomeReceiver)} which is provided by the {@code pendingControl#onResult(CallControl)}.
      * <p>
-     * The difference between this API call and {@link TelecomManager#placeCall(Uri, Bundle)} or
-     * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)} is that this API
-     * will asynchronously provide an update on whether the new call was added successfully via
-     * an {@link OutcomeReceiver}.  Additionally, callbacks will run on the executor thread that was
-     * passed in.
-     *
      * <p>
-     * Note: Only packages that register with
+     * <p>
+     * <b>Call Lifecycle</b>: Your app is given foreground execution priority as long as you have a
+     * valid call and are posting a {@link android.app.Notification.CallStyle} notification.
+     * When your application is given foreground execution priority, your app is treated as a
+     * foreground service. Foreground execution priority will prevent the
+     * {@link android.app.ActivityManager} from killing your application when it is placed the
+     * background. Foreground execution priority is removed from your app when all of your app's
+     * calls terminate or your app no longer posts a valid notification.
+     * <p>
+     * <p>
+     * <p>
+     * <b>Note</b>: Only packages that register with
      * {@link PhoneAccount#CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS}
      * can utilize this API. {@link PhoneAccount}s that set the capabilities
      * {@link PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION},
      * {@link PhoneAccount#CAPABILITY_CALL_PROVIDER},
      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}
      * are not supported and will cause an exception to be thrown.
-     *
      * <p>
-     * Usage example:
+     * <p>
+     * <p>
+     * <b>Usage example:</b>
      * <pre>
-     *
-     *  // An app should first define their own construct of a Call that overrides all the
-     *  // {@link CallControlCallback}s and {@link CallEventCallback}s
-     *  private class MyVoipCall {
-     *   public String callId = "";
-     *
-     *   public CallControlCallEventCallback handshakes = new
-     *                       CallControlCallEventCallback() {
-     *                         // override/ implement all {@link CallControlCallback}s
+     *  // Its up to your app on how you want to wrap the objects. One such implementation can be:
+     *  class MyVoipCall {
+     *    ...
+     *      public CallControlCallEventCallback handshakes = new  CallControlCallback() {
+     *                         ...
      *                        }
-     *   public CallEventCallback events = new
-     *                       CallEventCallback() {
-     *                         // override/ implement all {@link CallEventCallback}s
+     *
+     *      public CallEventCallback events = new CallEventCallback() {
+     *                         ...
      *                        }
-     *   public MyVoipCall(String id){
-     *       callId = id;
+     *
+     *      public MyVoipCall(String id){
+     *          ...
+     *      }
      *  }
      *
-     * PhoneAccountHandle handle = new PhoneAccountHandle(
-     *                          new ComponentName("com.example.voip.app",
-     *                                            "com.example.voip.app.NewCallActivity"), "123");
-     *
-     * CallAttributes callAttributes = new CallAttributes.Builder(handle,
-     *                                             CallAttributes.DIRECTION_OUTGOING,
-     *                                            "John Smith", Uri.fromParts("tel", "123", null))
-     *                                            .build();
-     *
      * MyVoipCall myFirstOutgoingCall = new MyVoipCall("1");
      *
-     * telecomManager.addCall(callAttributes, Runnable::run, new OutcomeReceiver() {
+     * telecomManager.addCall(callAttributes,
+     *                        Runnable::run,
+     *                        new OutcomeReceiver() {
      *                              public void onResult(CallControl callControl) {
-     *                                 // The call has been added successfully
+     *                                 // The call has been added successfully. For demonstration
+     *                                 // purposes, the call is disconnected immediately ...
+     *                                 callControl.disconnect(
+     *                                                 new DisconnectCause(DisconnectCause.LOCAL) )
      *                              }
-     *                           }, myFirstOutgoingCall.handshakes, myFirstOutgoingCall.events);
+     *                           },
+     *                           myFirstOutgoingCall.handshakes,
+     *                           myFirstOutgoingCall.events);
      * </pre>
      *
-     * @param callAttributes    attributes of the new call (incoming or outgoing, address, etc. )
-     * @param executor          thread to run background CallEventCallback updates on
-     * @param pendingControl    OutcomeReceiver that receives the result of addCall transaction
-     * @param handshakes        object that overrides {@link CallControlCallback}s
-     * @param events            object that overrides {@link CallEventCallback}s
+     * @param callAttributes attributes of the new call (incoming or outgoing, address, etc.)
+     * @param executor       execution context to run {@link CallControlCallback} updates on
+     * @param pendingControl Receives the result of addCall transaction. Upon success, a
+     *                       CallControl object is provided which can be used to do things like
+     *                       disconnect the call that was added.
+     * @param handshakes     callback that receives <b>actionable</b> updates that originate from
+     *                       Telecom.
+     * @param events         callback that receives <b>non</b>-actionable updates that originate
+     *                       from Telecom.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_OWN_CALLS)
     @SuppressLint("SamShouldBeLast")
diff --git a/telephony/java/android/telephony/satellite/AntennaDirection.aidl b/telephony/java/android/telephony/satellite/AntennaDirection.aidl
new file mode 100644
index 0000000..c838f6f
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/AntennaDirection.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.telephony.satellite;
+
+parcelable AntennaDirection;
diff --git a/telephony/java/android/telephony/satellite/AntennaDirection.java b/telephony/java/android/telephony/satellite/AntennaDirection.java
new file mode 100644
index 0000000..02b0bc7
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/AntennaDirection.java
@@ -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 android.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Antenna direction is provided as X/Y/Z values corresponding to the direction of the antenna
+ * main lobe as a unit vector in CTIA coordinate system (as specified in Appendix A of Wireless
+ * device CTIA OTAn test plan). CTIA coordinate system is defined relative to device’s screen
+ * when the device is held in default portrait mode with screen facing the user:
+ *
+ * Z axis is vertical along the plane of the device with positive Z pointing up and negative z
+ * pointing towards bottom of the device
+ * Y axis is horizontal along the plane of the device with positive Y pointing towards right of
+ * the phone screen and negative Y pointing towards left
+ * X axis is orthogonal to the Y-Z plane (phone screen), pointing away from the phone screen for
+ * positive X and pointing away from back of the phone for negative X.
+ * @hide
+ */
+public final class AntennaDirection implements Parcelable {
+    /** Antenna x axis direction. */
+    private float mX;
+
+    /** Antenna y axis direction. */
+    private float mY;
+
+    /** Antenna z axis direction. */
+    private float mZ;
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public AntennaDirection(float x, float y, float z) {
+        mX = x;
+        mY = y;
+        mZ = z;
+    }
+
+    private AntennaDirection(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeFloat(mX);
+        out.writeFloat(mY);
+        out.writeFloat(mZ);
+    }
+
+    @NonNull
+    public static final Creator<AntennaDirection> CREATOR =
+            new Creator<>() {
+                @Override
+                public AntennaDirection createFromParcel(Parcel in) {
+                    return new AntennaDirection(in);
+                }
+
+                @Override
+                public AntennaDirection[] newArray(int size) {
+                    return new AntennaDirection[size];
+                }
+            };
+
+    @Override
+    @NonNull public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("X:");
+        sb.append(mX);
+        sb.append(",");
+
+        sb.append("Y:");
+        sb.append(mY);
+        sb.append(",");
+
+        sb.append("Z:");
+        sb.append(mZ);
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        AntennaDirection that = (AntennaDirection) o;
+        return mX == that.mX
+                && mY == that.mY
+                && mZ == that.mZ;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mX, mY, mZ);
+    }
+
+    public float getX() {
+        return mX;
+    }
+
+    public float getY() {
+        return mY;
+    }
+
+    public float getZ() {
+        return mZ;
+    }
+
+    private void readFromParcel(Parcel in) {
+        mX = in.readFloat();
+        mY = in.readFloat();
+        mZ = in.readFloat();
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/AntennaPosition.aidl b/telephony/java/android/telephony/satellite/AntennaPosition.aidl
new file mode 100644
index 0000000..0052562
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/AntennaPosition.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.telephony.satellite;
+
+parcelable AntennaPosition;
diff --git a/telephony/java/android/telephony/satellite/AntennaPosition.java b/telephony/java/android/telephony/satellite/AntennaPosition.java
new file mode 100644
index 0000000..eefc8b0
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/AntennaPosition.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Antenna Position received from satellite modem which gives information about antenna
+ * direction to be used with satellite communication and suggested device hold positions.
+ * @hide
+ */
+public final class AntennaPosition implements Parcelable {
+    /** Antenna direction used for satellite communication. */
+    @NonNull AntennaDirection mAntennaDirection;
+
+    /** Enum corresponding to device hold position to be used by the end user. */
+    @SatelliteManager.DeviceHoldPosition int mSuggestedHoldPosition;
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public AntennaPosition(@NonNull AntennaDirection antennaDirection, int suggestedHoldPosition) {
+        mAntennaDirection = antennaDirection;
+        mSuggestedHoldPosition = suggestedHoldPosition;
+    }
+
+    private AntennaPosition(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeParcelable(mAntennaDirection, flags);
+        out.writeInt(mSuggestedHoldPosition);
+    }
+
+    @NonNull
+    public static final Creator<AntennaPosition> CREATOR =
+            new Creator<>() {
+                @Override
+                public AntennaPosition createFromParcel(Parcel in) {
+                    return new AntennaPosition(in);
+                }
+
+                @Override
+                public AntennaPosition[] newArray(int size) {
+                    return new AntennaPosition[size];
+                }
+            };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        AntennaPosition that = (AntennaPosition) o;
+        return Objects.equals(mAntennaDirection, that.mAntennaDirection)
+                && mSuggestedHoldPosition == that.mSuggestedHoldPosition;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAntennaDirection, mSuggestedHoldPosition);
+    }
+
+    @Override
+    @NonNull public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("antennaDirection:");
+        sb.append(mAntennaDirection);
+        sb.append(",");
+
+        sb.append("suggestedHoldPosition:");
+        sb.append(mSuggestedHoldPosition);
+        return sb.toString();
+    }
+
+    @NonNull
+    public AntennaDirection getAntennaDirection() {
+        return mAntennaDirection;
+    }
+
+    @SatelliteManager.DeviceHoldPosition
+    public int getSuggestedHoldPosition() {
+        return mSuggestedHoldPosition;
+    }
+
+    private void readFromParcel(Parcel in) {
+        mAntennaDirection = in.readParcelable(AntennaDirection.class.getClassLoader(),
+                AntennaDirection.class);
+        mSuggestedHoldPosition = in.readInt();
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
index 87c8db3..0092890 100644
--- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
@@ -21,7 +21,10 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -44,15 +47,25 @@
     private int mMaxBytesPerOutgoingDatagram;
 
     /**
+     * Antenna Position received from satellite modem which gives information about antenna
+     * direction to be used with satellite communication and suggested device hold positions.
+     * Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition
+     */
+    @NonNull
+    private Map<Integer, AntennaPosition> mAntennaPositionMap;
+
+    /**
      * @hide
      */
     @UnsupportedAppUsage
     public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies,
-            boolean isPointingRequired, int maxBytesPerOutgoingDatagram) {
+            boolean isPointingRequired, int maxBytesPerOutgoingDatagram,
+            @NonNull Map<Integer, AntennaPosition> antennaPositionMap) {
         mSupportedRadioTechnologies = supportedRadioTechnologies == null
                 ? new HashSet<>() : supportedRadioTechnologies;
         mIsPointingRequired = isPointingRequired;
         mMaxBytesPerOutgoingDatagram = maxBytesPerOutgoingDatagram;
+        mAntennaPositionMap = antennaPositionMap;
     }
 
     private SatelliteCapabilities(Parcel in) {
@@ -77,6 +90,17 @@
 
         out.writeBoolean(mIsPointingRequired);
         out.writeInt(mMaxBytesPerOutgoingDatagram);
+
+        if (mAntennaPositionMap != null && !mAntennaPositionMap.isEmpty()) {
+            int size = mAntennaPositionMap.size();
+            out.writeInt(size);
+            for (Map.Entry<Integer, AntennaPosition> entry : mAntennaPositionMap.entrySet()) {
+                out.writeInt(entry.getKey());
+                out.writeParcelable(entry.getValue(), flags);
+            }
+        } else {
+            out.writeInt(0);
+        }
     }
 
     @NonNull public static final Creator<SatelliteCapabilities> CREATOR = new Creator<>() {
@@ -109,11 +133,32 @@
         sb.append(mIsPointingRequired);
         sb.append(",");
 
-        sb.append("maxBytesPerOutgoingDatagram");
+        sb.append("maxBytesPerOutgoingDatagram:");
         sb.append(mMaxBytesPerOutgoingDatagram);
+        sb.append(",");
+
+        sb.append("antennaPositionMap:");
+        sb.append(mAntennaPositionMap);
         return sb.toString();
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SatelliteCapabilities that = (SatelliteCapabilities) o;
+        return Objects.equals(mSupportedRadioTechnologies, that.mSupportedRadioTechnologies)
+                && mIsPointingRequired == that.mIsPointingRequired
+                && mMaxBytesPerOutgoingDatagram == that.mMaxBytesPerOutgoingDatagram
+                && Objects.equals(mAntennaPositionMap, that.mAntennaPositionMap);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSupportedRadioTechnologies, mIsPointingRequired,
+                mMaxBytesPerOutgoingDatagram, mAntennaPositionMap);
+    }
+
     /**
      * @return The list of technologies supported by the satellite modem.
      */
@@ -141,6 +186,16 @@
         return mMaxBytesPerOutgoingDatagram;
     }
 
+    /**
+     * Antenna Position received from satellite modem which gives information about antenna
+     * direction to be used with satellite communication and suggested device hold positions.
+     * @return Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition
+     */
+    @NonNull
+    public Map<Integer, AntennaPosition> getAntennaPositionMap() {
+        return mAntennaPositionMap;
+    }
+
     private void readFromParcel(Parcel in) {
         mSupportedRadioTechnologies = new HashSet<>();
         int numSupportedRadioTechnologies = in.readInt();
@@ -152,5 +207,14 @@
 
         mIsPointingRequired = in.readBoolean();
         mMaxBytesPerOutgoingDatagram = in.readInt();
+
+        mAntennaPositionMap = new HashMap<>();
+        int antennaPositionMapSize = in.readInt();
+        for (int i = 0; i < antennaPositionMapSize; i++) {
+            int key = in.readInt();
+            AntennaPosition antennaPosition = in.readParcelable(
+                    AntennaPosition.class.getClassLoader(), AntennaPosition.class);
+            mAntennaPositionMap.put(key, antennaPosition);
+        }
     }
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 20f9bc8b..5681ab2 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -334,6 +334,46 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface NTRadioTechnology {}
 
+    /** Suggested device hold position is unknown. */
+    public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0;
+    /** User is suggested to hold the device in portrait mode. */
+    public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1;
+    /** User is suggested to hold the device in landscape mode with left hand. */
+    public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2;
+    /** User is suggested to hold the device in landscape mode with right hand. */
+    public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3;
+
+    /** @hide */
+    @IntDef(prefix = {"DEVICE_HOLD_POSITION_"}, value = {
+            DEVICE_HOLD_POSITION_UNKNOWN,
+            DEVICE_HOLD_POSITION_PORTRAIT,
+            DEVICE_HOLD_POSITION_LANDSCAPE_LEFT,
+            DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT
+       })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DeviceHoldPosition {}
+
+    /** Display mode is unknown. */
+    public static final int DISPLAY_MODE_UNKNOWN = 0;
+    /** Display mode of the device used for satellite communication for non-foldable phones. */
+    public static final int DISPLAY_MODE_FIXED = 1;
+    /** Display mode of the device used for satellite communication for foldabale phones when the
+     * device is opened. */
+    public static final int DISPLAY_MODE_OPENED = 2;
+    /** Display mode of the device used for satellite communication for foldabable phones when the
+     * device is closed. */
+    public static final int DISPLAY_MODE_CLOSED = 3;
+
+    /** @hide */
+    @IntDef(prefix = {"ANTENNA_POSITION_"}, value = {
+            DISPLAY_MODE_UNKNOWN,
+            DISPLAY_MODE_FIXED,
+            DISPLAY_MODE_OPENED,
+            DISPLAY_MODE_CLOSED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DisplayMode {}
+
     /**
      * Request to enable or disable the satellite modem and demo mode. If the satellite modem is
      * enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
index cd69da1..eaf96ab 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
@@ -17,7 +17,7 @@
 package android.telephony.satellite.stub;
 
 import android.telephony.satellite.stub.NTRadioTechnology;
-
+import android.telephony.satellite.AntennaPosition;
 /**
  * {@hide}
  */
@@ -36,4 +36,14 @@
      * The maximum number of bytes per datagram that can be sent over satellite.
      */
     int maxBytesPerOutgoingDatagram;
+
+    /**
+     * Keys which are used to fill mAntennaPositionMap.
+     */
+    int[] antennaPositionKeys;
+
+    /**
+     * Antenna Position for different display modes received from satellite modem.
+     */
+    AntennaPosition[] antennaPositionValues;
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index cbdf38ae..ee9d6c1 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2989,4 +2989,14 @@
      * {@code false} otherwise.
      */
     boolean setSatelliteServicePackageName(in String servicePackageName);
+
+    /**
+     * This API can be used by only CTS to update the timeout duration in milliseconds that
+     * satellite should stay at listening mode to wait for the next incoming page before disabling
+     * listening mode.
+     *
+     * @param timeoutMillis The timeout duration in millisecond.
+     * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
+     */
+    boolean setSatelliteListeningTimeoutDuration(in long timeoutMillis);
 }
diff --git a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
index cfebf34..4299e0d6 100644
--- a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
+++ b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
@@ -87,7 +87,7 @@
     // avoid flakiness we run these tests multiple times, allowing progressively longer between
     // code loading and checking the logs on each try.)
     private static final int AUDIT_LOG_RETRIES = 10;
-    private static final int RETRY_DELAY_MS = 2_000;
+    private static final int RETRY_DELAY_MS = 500;
 
     private static Context sContext;
     private static int sMyUid;
@@ -253,7 +253,7 @@
                 "/DynamicCodeLoggerNativeExecutable", privateCopyFile);
 
         EventLog.writeEvent(EventLog.getTagCode("auditd"),
-                "type=1400 avc: granted { execute_no_trans } "
+                "type=1400 avc:  granted  { execute_no_trans } "
                         + "path=\"" + privateCopyFile + "\" "
                         + "scontext=u:r:untrusted_app: "
                         + "tcontext=u:object_r:app_data_file: "
diff --git a/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl b/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl
index 87aad10..6e59b04 100644
--- a/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl
+++ b/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl
@@ -52,4 +52,10 @@
 
     @EnforcePermission(anyOf={"INTERNET", "VIBRATE"})
     void ProtectedByInternetOrVibrate();
+
+    @RequiresNoPermission
+    void NotProtected();
+
+    @PermissionManuallyEnforced
+    void ManuallyProtected();
 }
diff --git a/tests/EnforcePermission/perf-app/Android.bp b/tests/EnforcePermission/perf-app/Android.bp
new file mode 100644
index 0000000..b494bb7
--- /dev/null
+++ b/tests/EnforcePermission/perf-app/Android.bp
@@ -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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "EnforcePermissionPerfTests",
+    srcs: [
+        "src/**/*.java",
+        ":frameworks-enforce-permission-test-aidl",
+    ],
+    static_libs: [
+        "EnforcePermissionTestLib",
+        "androidx.benchmark_benchmark-common",
+        "androidx.benchmark_benchmark-junit4",
+        "apct-perftests-utils",
+        "collector-device-lib",
+        "androidx.test.rules",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    data: [
+        ":EnforcePermissionTestHelper",
+        ":perfetto_artifacts",
+        "perfetto.textproto",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+    test_suites: ["device-tests"],
+}
diff --git a/tests/EnforcePermission/perf-app/AndroidManifest.xml b/tests/EnforcePermission/perf-app/AndroidManifest.xml
new file mode 100644
index 0000000..900270d
--- /dev/null
+++ b/tests/EnforcePermission/perf-app/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="android.tests.enforcepermission.tests">
+
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <!-- Required by perfetto -->
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <queries>
+        <package android:name="android.tests.enforcepermission.service" />
+    </queries>
+
+    <application>
+      <uses-library android:name="android.test.runner" />
+      <profileable android:shell="true" />
+      <!-- Instance of the Service within the app. This is to test performance for same-process calls. -->
+      <service android:name=".TestService" />
+    </application>
+    <instrumentation android:name="androidx.benchmark.junit4.AndroidBenchmarkRunner"
+                     android:targetPackage="android.tests.enforcepermission.tests"/>
+</manifest>
diff --git a/tests/EnforcePermission/perf-app/AndroidTest.xml b/tests/EnforcePermission/perf-app/AndroidTest.xml
new file mode 100644
index 0000000..3bc1d2d
--- /dev/null
+++ b/tests/EnforcePermission/perf-app/AndroidTest.xml
@@ -0,0 +1,43 @@
+<?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.
+-->
+<configuration description="Runs EnforcePermission Perf Tests">
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file" key="perfetto.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+      <option name="test-file-name" value="EnforcePermissionTestHelper.apk"/>
+      <option name="test-file-name" value="EnforcePermissionPerfTests.apk"/>
+      <option name="cleanup-apks" value="true" />
+    </target_preparer>
+
+    <option name="isolated-storage" value="false" />
+
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="perfetto_file_path" />
+        <option name="collect-on-run-ended-only" value="false" />
+    </metrics_collector>
+
+    <option name="test-tag" value="EnforcePermissionTests"/>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.tests.enforcepermission.tests"/>
+        <option name="device-listeners" value="android.device.collectors.PerfettoListener" />
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+        <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+    </test>
+</configuration>
diff --git a/tests/EnforcePermission/perf-app/perfetto.textproto b/tests/EnforcePermission/perf-app/perfetto.textproto
new file mode 100644
index 0000000..8a3eea4
--- /dev/null
+++ b/tests/EnforcePermission/perf-app/perfetto.textproto
@@ -0,0 +1,154 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Based on trace_config_detailed.textproto
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 1000
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 10000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers {
+  size_kb: 32768
+  fill_policy: RING_BUFFER
+}
+
+# procfs polling
+buffers {
+  size_kb: 8192
+  fill_policy: RING_BUFFER
+}
+
+# perf memory
+buffers {
+  size_kb: 65536
+  fill_policy: RING_BUFFER
+}
+
+data_sources {
+  config {
+    name: "linux.ftrace"
+    target_buffer: 0
+    ftrace_config {
+      throttle_rss_stat: true
+      # These parameters affect only the kernel trace buffer size and how
+      # frequently it gets moved into the userspace buffer defined above.
+      buffer_size_kb: 16384
+      drain_period_ms: 250
+
+      # Store certain high-volume "sched" ftrace events in a denser format
+      # (falling back to the default format if not supported by the tracer).
+      compact_sched {
+        enabled: true
+      }
+
+      # Enables symbol name resolution against /proc/kallsyms
+      symbolize_ksyms: true
+      # Parse kallsyms before acknowledging that the ftrace data source has been started. In
+      # combination with "perfetto --background-wait" as the consumer, it lets us defer the
+      # test we're tracing until after the cpu has quieted down from the cpu-bound kallsyms parsing.
+      initialize_ksyms_synchronously_for_testing: true
+      # Avoid re-parsing kallsyms on every test run, as it takes 200-500ms per run. See b/239951079
+      ksyms_mem_policy: KSYMS_RETAIN
+
+      # We need to do process tracking to ensure kernel ftrace events targeted at short-lived
+      # threads are associated correctly
+      ftrace_events: "task/task_newtask"
+      ftrace_events: "task/task_rename"
+      ftrace_events: "sched/sched_process_exit"
+      ftrace_events: "sched/sched_process_free"
+
+      # Memory events
+      ftrace_events: "rss_stat"
+      ftrace_events: "ion_heap_shrink"
+      ftrace_events: "ion_heap_grow"
+      ftrace_events: "ion/ion_stat"
+      ftrace_events: "dmabuf_heap/dma_heap_stat"
+      ftrace_events: "oom_score_adj_update"
+      ftrace_events: "gpu_mem/gpu_mem_total"
+      ftrace_events: "fastrpc/fastrpc_dma_stat"
+
+      # Power events
+      ftrace_events: "power/suspend_resume"
+      ftrace_events: "power/cpu_frequency"
+      ftrace_events: "power/cpu_idle"
+      ftrace_events: "power/gpu_frequency"
+
+      # Old (kernel) LMK
+      ftrace_events: "lowmemorykiller/lowmemory_kill"
+
+      atrace_apps: "*"
+
+      atrace_categories: "am"
+      atrace_categories: "aidl"
+      atrace_categories: "bionic"
+      atrace_categories: "camera"
+      atrace_categories: "wm"
+      atrace_categories: "dalvik"
+      atrace_categories: "sched"
+      atrace_categories: "freq"
+      atrace_categories: "gfx"
+      atrace_categories: "view"
+      atrace_categories: "webview"
+      atrace_categories: "input"
+      atrace_categories: "hal"
+      atrace_categories: "binder_driver"
+      atrace_categories: "sync"
+      atrace_categories: "workq"
+      atrace_categories: "res"
+      atrace_categories: "power"
+
+    }
+  }
+}
+
+data_sources {
+  config {
+    name: "linux.process_stats"
+    target_buffer: 1
+    process_stats_config {
+      proc_stats_poll_ms: 10000
+    }
+  }
+}
+
+data_sources {
+  config {
+    name: "linux.perf"
+    target_buffer: 2
+    perf_event_config {
+      timebase {
+        frequency: 80
+      }
+      callstack_sampling {
+        scope {
+          target_cmdline: "android.tests.enforcepermission.tests"
+          target_cmdline: "android.tests.enforcepermission.service"
+          target_cmdline: "system_server"
+        }
+        kernel_frames: true
+      }
+    }
+  }
+}
diff --git a/tests/EnforcePermission/perf-app/src/android/tests/enforcepermission/tests/ServicePerfTest.java b/tests/EnforcePermission/perf-app/src/android/tests/enforcepermission/tests/ServicePerfTest.java
new file mode 100644
index 0000000..7cbf567
--- /dev/null
+++ b/tests/EnforcePermission/perf-app/src/android/tests/enforcepermission/tests/ServicePerfTest.java
@@ -0,0 +1,169 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tests.enforcepermission.tests;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.tests.enforcepermission.IProtected;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/** Performance tests for EnforcePermission annotation.
+ *
+ * Permission check results are cached on the service side as it relies on
+ * PermissionManager. It means that only the first request will trigger a
+ * lookup to system_server. Subsequent requests will use the cached result. As
+ * this timing is similar to a permission check for a service hosted in
+ * system_server, we keep this cache active for the tests. The BenchmarkState
+ * used by PerfStatusReporter includes a warm-up stage. It means that the extra
+ * time taken by the first request will not be reflected in the outcome of the
+ * test.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ServicePerfTest {
+
+    private static final String TAG = "EnforcePermission.PerfTests";
+    private static final String SERVICE_PACKAGE = "android.tests.enforcepermission.service";
+    private static final String LOCAL_SERVICE_PACKAGE = "android.tests.enforcepermission.tests";
+    private static final int SERVICE_TIMEOUT_SEC = 5;
+
+    @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    private Context mContext;
+    private volatile ServiceConnection mServiceConnection;
+
+    private void bindService(Intent intent) throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mServiceConnection = new ServiceConnection();
+        assertTrue(mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE));
+    }
+
+    public void bindRemoteService() throws Exception {
+        Log.d(TAG, "bindRemoteService");
+        Intent intent = new Intent();
+        intent.setClassName(SERVICE_PACKAGE, SERVICE_PACKAGE + ".TestService");
+        bindService(intent);
+    }
+
+    public void bindLocalService() throws Exception {
+        Log.d(TAG, "bindLocalService");
+        Intent intent = new Intent();
+        intent.setClassName(LOCAL_SERVICE_PACKAGE, SERVICE_PACKAGE + ".TestService");
+        bindService(intent);
+    }
+
+    @After
+    public void unbindTestService() throws Exception {
+        mContext.unbindService(mServiceConnection);
+    }
+
+    private static final class ServiceConnection implements android.content.ServiceConnection {
+        private volatile CompletableFuture<IProtected> mFuture = new CompletableFuture<>();
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mFuture.complete(IProtected.Stub.asInterface(service));
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            mFuture = new CompletableFuture<>();
+        }
+
+        public IProtected get() {
+            try {
+                return mFuture.get(SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS);
+            } catch (ExecutionException | InterruptedException | TimeoutException e) {
+                throw new RuntimeException("Unable to reach TestService: " + e.toString());
+            }
+        }
+    }
+
+    @Test
+    public void testAnnotatedPermission() throws Exception {
+        bindRemoteService();
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mServiceConnection.get().ProtectedByInternet();
+        }
+    }
+
+    @Test
+    public void testNoPermission() throws Exception {
+        bindRemoteService();
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mServiceConnection.get().NotProtected();
+        }
+    }
+
+    @Test
+    public void testManuallyProtected() throws Exception {
+        bindRemoteService();
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mServiceConnection.get().ManuallyProtected();
+        }
+    }
+
+    @Test
+    public void testAnnotatedPermissionLocal()
+            throws Exception {
+        bindLocalService();
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mServiceConnection.get().ProtectedByInternet();
+        }
+    }
+
+    @Test
+    public void testNoPermissionLocal() throws Exception {
+        bindLocalService();
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mServiceConnection.get().NotProtected();
+        }
+    }
+
+    @Test
+    public void testManuallyProtectedLocal() throws Exception {
+        bindLocalService();
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mServiceConnection.get().ManuallyProtected();
+        }
+    }
+}
diff --git a/tests/EnforcePermission/service-app/Android.bp b/tests/EnforcePermission/service-app/Android.bp
index a4ac1d7..7878215 100644
--- a/tests/EnforcePermission/service-app/Android.bp
+++ b/tests/EnforcePermission/service-app/Android.bp
@@ -21,6 +21,14 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+android_library {
+    name: "EnforcePermissionTestLib",
+    srcs: [
+        "src/**/*.java",
+        ":frameworks-enforce-permission-test-aidl",
+    ],
+}
+
 android_test_helper_app {
     name: "EnforcePermissionTestHelper",
     srcs: [
diff --git a/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java
index 0a3af1a..8b809cf 100644
--- a/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java
+++ b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java
@@ -17,6 +17,7 @@
 package android.tests.enforcepermission.service;
 
 import android.annotation.EnforcePermission;
+import android.annotation.RequiresNoPermission;
 import android.app.Service;
 import android.content.ComponentName;
 import android.content.Context;
@@ -172,5 +173,15 @@
         public void ProtectedByInternetOrVibrate() {
             ProtectedByInternetOrVibrate_enforcePermission();
         }
+
+        @Override
+        @RequiresNoPermission
+        public void NotProtected() {
+        }
+
+        @Override
+        public void ManuallyProtected() {
+            enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "access denied");
+        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 314b9e4..f389e13 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -260,7 +260,7 @@
                     snapshotLayers
                         .mapNotNull { snapshotLayer -> snapshotLayer.layer.visibleRegion }
                         .toTypedArray()
-                val snapshotRegion = RegionSubject(visibleAreas, this, timestamp)
+                val snapshotRegion = RegionSubject(visibleAreas, timestamp)
                 val appVisibleRegion = it.visibleRegion(component)
                 if (snapshotRegion.region.isNotEmpty) {
                     snapshotRegion.coversExactly(appVisibleRegion.region)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
index 6066d2e..34fa921 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
@@ -88,7 +88,7 @@
                         imeSnapshotLayers
                             .mapNotNull { imeSnapshotLayer -> imeSnapshotLayer.layer.visibleRegion }
                             .toTypedArray()
-                    val imeVisibleRegion = RegionSubject(visibleAreas, this, timestamp)
+                    val imeVisibleRegion = RegionSubject(visibleAreas, timestamp)
                     val appVisibleRegion = it.visibleRegion(imeTestApp)
                     if (imeVisibleRegion.region.isNotEmpty) {
                         imeVisibleRegion.coversAtMost(appVisibleRegion.region)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index 2fff001..d5208e0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -73,7 +73,16 @@
     @Test
     @IwTest(focusArea = "ime")
     override fun cujCompleted() {
-        super.cujCompleted()
+        runAndIgnoreAssumptionViolation { entireScreenCovered() }
+        runAndIgnoreAssumptionViolation { statusBarLayerIsVisibleAtStartAndEnd() }
+        runAndIgnoreAssumptionViolation { statusBarLayerPositionAtStartAndEnd() }
+        runAndIgnoreAssumptionViolation { statusBarWindowIsAlwaysVisible() }
+        runAndIgnoreAssumptionViolation { visibleWindowsShownMoreThanOneConsecutiveEntry() }
+        runAndIgnoreAssumptionViolation { taskBarLayerIsVisibleAtStartAndEnd() }
+        runAndIgnoreAssumptionViolation { taskBarWindowIsAlwaysVisible() }
+        runAndIgnoreAssumptionViolation { navBarLayerIsVisibleAtStartAndEnd() }
+        runAndIgnoreAssumptionViolation { navBarWindowIsAlwaysVisible() }
+        runAndIgnoreAssumptionViolation { navBarWindowIsVisibleAtStartAndEnd() }
         imeLayerBecomesInvisible()
         imeWindowBecomesInvisible()
     }
diff --git a/tests/OdmApps/Android.bp b/tests/OdmApps/Android.bp
index 5f03aa2..a5c6d65 100644
--- a/tests/OdmApps/Android.bp
+++ b/tests/OdmApps/Android.bp
@@ -28,5 +28,6 @@
     test_suites: ["device-tests"],
     data: [
         ":TestOdmApp",
+        ":TestOdmPrivApp",
     ],
 }
diff --git a/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml b/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml
index d2653d0..f20dd42 100644
--- a/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml
+++ b/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml
@@ -18,6 +18,6 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
     <gradient
         android:startColor="#000000"
-        android:endColor="#181818"
+        android:endColor="#222222"
         android:angle="0"/>
 </shape>
\ No newline at end of file
diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h
index a146466..e2c1614 100644
--- a/tools/aapt/SdkConstants.h
+++ b/tools/aapt/SdkConstants.h
@@ -49,6 +49,7 @@
     SDK_S = 31,
     SDK_S_V2 = 32,
     SDK_TIRAMISU = 33,
+    SDK_UPSIDE_DOWN_CAKE = 34,
     SDK_CUR_DEVELOPMENT = 10000,
 };
 
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 40bcef7..e47704ea 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -59,6 +59,7 @@
   SDK_S = 31,
   SDK_S_V2 = 32,
   SDK_TIRAMISU = 33,
+  SDK_UPSIDE_DOWN_CAKE = 34,
   SDK_CUR_DEVELOPMENT = 10000,
 };
 
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index 25fbabc..166fbdd 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -163,11 +163,11 @@
         /**
          * Sets the displayed connection strength of the remote device to the internet.
          *
-         * @param connectionStrength Connection strength in range 0 to 3.
+         * @param connectionStrength Connection strength in range 0 to 4.
          * @return Returns the Builder object.
          */
         @NonNull
-        public Builder setConnectionStrength(@IntRange(from = 0, to = 3) int connectionStrength) {
+        public Builder setConnectionStrength(@IntRange(from = 0, to = 4) int connectionStrength) {
             mConnectionStrength = connectionStrength;
             return this;
         }
@@ -205,8 +205,8 @@
         if (batteryPercentage < 0 || batteryPercentage > 100) {
             throw new IllegalArgumentException("BatteryPercentage must be in range 0-100");
         }
-        if (connectionStrength < 0 || connectionStrength > 3) {
-            throw new IllegalArgumentException("ConnectionStrength must be in range 0-3");
+        if (connectionStrength < 0 || connectionStrength > 4) {
+            throw new IllegalArgumentException("ConnectionStrength must be in range 0-4");
         }
     }
 
@@ -265,9 +265,9 @@
     /**
      * Gets the displayed connection strength of the remote device to the internet.
      *
-     * @return Returns the connection strength in range 0 to 3.
+     * @return Returns the connection strength in range 0 to 4.
      */
-    @IntRange(from = 0, to = 3)
+    @IntRange(from = 0, to = 4)
     public int getConnectionStrength() {
         return mConnectionStrength;
     }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index e5ef62b..feef049 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -35,6 +35,7 @@
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.RemoteException;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.R;
@@ -173,10 +174,15 @@
                     R.string.config_sharedConnectivityServicePackage);
             String serviceIntentAction = resources.getString(
                     R.string.config_sharedConnectivityServiceIntentAction);
+            if (TextUtils.isEmpty(servicePackageName) || TextUtils.isEmpty(serviceIntentAction)) {
+                Log.e(TAG, "To support shared connectivity service on this device, the"
+                        + " service's package name and intent action strings must not be empty");
+                return null;
+            }
             return new SharedConnectivityManager(context, servicePackageName, serviceIntentAction);
         } catch (Resources.NotFoundException e) {
             Log.e(TAG, "To support shared connectivity service on this device, the service's"
-                    + " package name and intent action string must be defined");
+                    + " package name and intent action strings must be defined");
         }
         return null;
     }
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
index b585bd5..a03a6c2 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
@@ -105,6 +105,13 @@
     }
 
     @Test
+    public void resourceStringsAreEmpty_createShouldReturnNull() {
+        when(mResources.getString(anyInt())).thenReturn("");
+
+        assertThat(SharedConnectivityManager.create(mContext)).isNull();
+    }
+
+    @Test
     public void bindingToServiceOnFirstCallbackRegistration() {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.registerCallback(mExecutor, mClientCallback);